diff --git a/default.nix b/default.nix index 9939795..f4859f6 100644 --- a/default.nix +++ b/default.nix @@ -1,18 +1,49 @@ { pkgs ? import (builtins.fetchTarball { - url = "https://github.com/NixOS/nixpkgs/archive/3e2237222b33d8b5754cc1cc3ee155cadd76770d.tar.gz"; + url = "https://github.com/NixOS/nixpkgs/archive/f4429fde23e1fb20ee27f264e74c28c619d2cebb.tar.gz"; }) {} -}: { - shell = { - javascript = pkgs.mkShell { - buildInputs = [ - pkgs.pkgsCross.ghcjs.buildPackages.haskell.compiler.ghcHEAD - pkgs.haskellPackages.cabal-install - ]; +}: + +let + inherit (pkgs.haskell.lib) doJailbreak; + + haskellPackages = pkgs.haskell.packages.ghcHEAD.override { + overrides = self: super: { + mkDerivation = args: super.mkDerivation ({ + doCheck = false; + doBenchmark = false; + doHoogle = true; + doHaddock = true; + enableLibraryProfiling = false; + enableExecutableProfiling = false; + } // args); + + htmlt = self.callCabal2nix "htmlt" ./. {}; + + + # th-compat = self.callCabal2nix "th-compat" ./packages/th-compat {}; + tagged = self.callCabal2nix "tagged" ../fullstack-app/packages/tagged {}; + th-abstraction = self.callCabal2nix "th-abstraction" ../fullstack-app/packages/th-abstraction {}; + vector = self.callCabal2nix "vector" ../fullstack-app/packages/vector/vector {}; + vector-stream = self.callCabal2nix "vector-stream" ../fullstack-app/packages/vector/vector-stream {}; + unordered-containers = self.callCabal2nix "unordered-containers" ../fullstack-app/packages/unordered-containers {}; + # text-short = self.callCabal2nix "text-short" ./packages/text-short {}; + generic-deriving = doJailbreak super.generic-deriving; + bifunctors = doJailbreak super.bifunctors; + semigroupoids = doJailbreak super.semigroupoids; + th-lift = doJailbreak super.th-lift; + th-expand-syns = doJailbreak super.th-expand-syns; + invariant = doJailbreak super.invariant; + free = doJailbreak super.free; + th-compat = doJailbreak super.th-compat; + lens = doJailbreak super.lens; }; - x86_64 = pkgs.mkShell { - buildInputs = [ - pkgs.haskell.compiler.ghcHEAD - ]; + }; + + result = { + pkgs = haskellPackages; + shell = pkgs.mkShell { + inputsFrom = [haskellPackages.htmlt.env]; }; }; -} +in + result diff --git a/examples/charts/App.hs b/examples/charts/App.hs new file mode 100644 index 0000000..568a3af --- /dev/null +++ b/examples/charts/App.hs @@ -0,0 +1,55 @@ +module App where + +import Charts qualified as Charts +import Control.Monad.State +import Data.Proxy +import Data.Text (Text) +import Data.Text qualified as Text +import GHC.Generics +import Clickable +import Clickable.FFI + + +data AppInstance = AppInstance + { state_var :: DynVar AppTab + } deriving (Generic) + +data AppTab + = ChartsTab Charts.ChartInstance + | HelpTab + +new :: ClickM AppInstance +new = do + charts_instance <- Charts.new + state_var <- newVar $ ChartsTab charts_instance + return AppInstance + { state_var + } + +html :: AppInstance -> HtmlM () +html self = do + el "style" $ text styles + div_ do + button_ do + text "Open Charts" + on @"click" do + inst <- Charts.new + modifyVar self.state_var $ const $ ChartsTab inst + button_ do + text "Open Help" + on @"click" $ modifyVar self.state_var $ const HelpTab + dyn $ self.state_var `mapVar` \case + ChartsTab inst -> Charts.html inst + HelpTab -> p_ $ text + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \ + \tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \ + \veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \ + \commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \ + \velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \ + \occaecat cupidatat non proident, sunt in culpa qui officia deserunt \ + \mollit anim id est laborum." + +styles :: Text +styles = "\ + \ \ + \ " diff --git a/examples/charts/Charts.hs b/examples/charts/Charts.hs new file mode 100644 index 0000000..224ea22 --- /dev/null +++ b/examples/charts/Charts.hs @@ -0,0 +1,103 @@ +module Charts where + +import Data.Proxy +import Control.Monad.State +import Data.Text (Text) +import Data.Text qualified as Text +import GHC.Generics + +import Clickable +import Clickable.FFI +import PairSelector qualified as PairSelector + +------------------------------------------------------- +-- EXAMPLE OF APPLICATION FEATURING FINANCIAL CHARTS -- +------------------------------------------------------- + +data ChartState = ChartState + { main_transform :: Transform2D + , mouse_position :: Point + , counter :: Int + } deriving (Show, Eq, Generic) + +data ChartInstance = ChartInstance + { state_var :: DynVar ChartState + , pair_selector_instance :: DynVar PairSelector.PairSelectorState + } deriving (Generic) + +new :: ClickM ChartInstance +new = do + pair_selector_instance <- PairSelector.new + state_var <- newVar ChartState + { main_transform = Transform2D 0 0 0 0 + , mouse_position = Point 0 0 + , counter = 0 + } + return ChartInstance {state_var, pair_selector_instance} + +html :: ChartInstance -> HtmlM () +html self = do + el "style" $ text styles + div_ [class_ "Charts-root"] do + h1_ $ text "Canvas with candle chart" + div_ do + PairSelector.html self.pair_selector_instance + div_ do + button_ do + text "Clickable this button" + on @"click" do + modifyVar self.state_var \s -> s {counter = s.counter + 1 } + button_ do + text "Print state" + on @"click" do + s <- readVar self.state_var + consoleLog $ Text.pack $ show s + span_ [] $ dynText $ self.state_var `mapVar` \s -> + "You clicked " <> Text.pack (show s.counter) <> " times" + canvas_ [class_ "Charts-canvas"] $ return () + +styles :: Text +styles = "\ + \.Charts-root {\ + \ max-width: 900;\ + \ width: 100%\ + \ padding: 16px;\ + \ margin: 0 auto;\ + \}\ + \.Charts-canvas {\ + \ width: 100%;\ + \ height: 550px;\ + \ border: solid 1px black;\ + \}\ + \ " + +data Transform2D = Transform2D + { a :: Double -- ^ X scaling + , c :: Double -- ^ X translation + , e :: Double -- ^ Y scaling + , f :: Double -- ^ Y translation + } deriving (Show, Eq, Generic) + +data Point = Point + { point_x :: Double + , point_y :: Double + } deriving (Eq, Show, Generic) + +-- setMouseCoords :: Point -> Edit ChartState +-- setMouseCoords p = Fld (Proxy @"mouse_position") (Ins p) + + +-- moveScreen :: Double -> Jet ChartState -> Jet ChartState +-- moveScreen dir old = +-- let +-- beg = negate old.position.main_transform.c / old.position.main_transform.a +-- end = (w - old.position.main_transform.c) / old.position.main_transform.a +-- inc = (end - beg) * dir +-- w = 1000 +-- c = old.position.main_transform.c - +-- (inc * old.position.main_transform.a) +-- new = old.position +-- { main_transform = old.position.main_transform {c} +-- } +-- in +-- Jet new (Fld (Proxy @"main_transform") (Fld (Proxy @"c") (Ins c)) : old.velocity) diff --git a/examples/charts/PairSelector.hs b/examples/charts/PairSelector.hs new file mode 100644 index 0000000..d82fee0 --- /dev/null +++ b/examples/charts/PairSelector.hs @@ -0,0 +1,45 @@ +module PairSelector where + +import Data.Proxy +import Data.Text (Text) +import Data.Text qualified as Text +import Data.Function hiding (on) +import GHC.Generics + +import Clickable + +data TradingPair = TradingPair + { base :: Text + , quote :: Text + , exchange :: Text + } deriving (Show, Eq, Generic) + +data PairSelectorState = PairSelectorState + { selected_pair :: Maybe TradingPair + , options :: [TradingPair] + } deriving (Show, Eq, Generic) + +new :: ClickM (DynVar PairSelectorState) +new = + newVar PairSelectorState + { selected_pair = Nothing + , options = [] + } + +html :: DynVar PairSelectorState -> HtmlM () +html self = do + el "style" $ text styles + div_ [class_ "PairSelector-root"] do + button_ do + text "Select pair" + on @"click" $ modifyVar self \s -> case s.selected_pair of + Just _ -> s {selected_pair = Nothing} + Nothing -> s {selected_pair = Just $ TradingPair "BTC" "USDT" "MEXC"} + span_ $ dynText $ self `mapVar` \s -> s.selected_pair & maybe + "Nothing selected" + (("Selected pair: " <>) . Text.pack . show) + +styles :: Text +styles = "\ + \ \ + \ " diff --git a/examples/charts/charts.hs b/examples/charts/charts.hs new file mode 100644 index 0000000..bdac142 --- /dev/null +++ b/examples/charts/charts.hs @@ -0,0 +1,11 @@ +import Control.Monad.Reader +import Clickable +import "this" App qualified as App + +main :: IO () +main = return () + +foreign export ccall wasm_main :: IO () +wasm_main = do + _ <- attach $ liftClickM App.new >>= App.html + return () diff --git a/examples/counter/counter.hs b/examples/counter/counter.hs deleted file mode 100644 index f3fb5a1..0000000 --- a/examples/counter/counter.hs +++ /dev/null @@ -1,32 +0,0 @@ -import Control.Monad -import Control.Monad.Trans.Maybe -import HtmlT -import JavaScript.Compat.String qualified as JSS -import Text.Read (readMaybe) - -app :: Html () -app = do - -- First create a 'DynRef - counterRef <- newRef @Int 0 - div_ do - input_ [type_ "number"] do - -- Show the value inside - dynProp "value" $ JSS.pack . show <$> fromRef counterRef - -- Parse and update the value on each InputEvent - on "input" $ decodeEvent intDecoder $ writeRef counterRef - br_ - -- Decrease the value on each click - button_ do - on_ "click" $ modifyRef counterRef pred - text "-" - -- Increase the value on each click - button_ do - on_ "click" $ modifyRef counterRef succ - text "+" - where - intDecoder = - valueDecoder >=> MaybeT . pure . readMaybe . JSS.unpack - -main :: IO () -main = - void $ attachToBody app diff --git a/examples/simple-routing/Assets.hs b/examples/simple-routing/Assets.hs deleted file mode 100644 index 9410daa..0000000 --- a/examples/simple-routing/Assets.hs +++ /dev/null @@ -1,4241 +0,0 @@ -module Assets where - -import GHC.Generics -import JavaScript.Compat.String (JSString(..)) - -data Country = Country - { title :: JSString - , code :: JSString - , wiki_href :: JSString - , population :: Int - , flag_icon :: Maybe JSString - , region :: JSString - , subregion :: JSString - } deriving Generic - -countries :: [Country] -countries = - [ Country "China" "CN" "https://en.wikipedia.org/wiki/China" 1433783686 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Flag_of_the_People%27s_Republic_of_China.svg/23px-Flag_of_the_People%27s_Republic_of_China.svg.png") "Asia" "Eastern Asia" - , Country "India" "IN" "https://en.wikipedia.org/wiki/India" 1366417754 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/4/41/Flag_of_India.svg/23px-Flag_of_India.svg.png") "Asia" "Southern Asia" - , Country "United States" "us" "https://en.wikipedia.org/wiki/United_States" 329064917 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/23px-Flag_of_the_United_States.svg.png") "Americas" "Northern America" - , Country "Indonesia" "ID" "https://en.wikipedia.org/wiki/Indonesia" 270625568 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/Flag_of_Indonesia.svg/23px-Flag_of_Indonesia.svg.png") "Asia" "South-eastern Asia" - , Country "Pakistan" "PK" "https://en.wikipedia.org/wiki/Pakistan" 216565318 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/32/Flag_of_Pakistan.svg/23px-Flag_of_Pakistan.svg.png") "Asia" "Southern Asia" - , Country "Brazil" "BR" "https://en.wikipedia.org/wiki/Brazil" 211049527 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/0/05/Flag_of_Brazil.svg/22px-Flag_of_Brazil.svg.png") "Americas" "South America" - , Country "Nigeria" "NG" "https://en.wikipedia.org/wiki/Nigeria" 200963599 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Flag_of_Nigeria.svg/23px-Flag_of_Nigeria.svg.png") "Africa" "Western Africa" - , Country "Bangladesh" "BD" "https://en.wikipedia.org/wiki/Bangladesh" 163046161 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Flag_of_Bangladesh.svg/23px-Flag_of_Bangladesh.svg.png") "Asia" "Southern Asia" - , Country "Russia" "RU" "https://en.wikipedia.org/wiki/Russia" 145872256 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/f/f3/Flag_of_Russia.svg/23px-Flag_of_Russia.svg.png") "Europe" "Eastern Europe" - , Country "Mexico" "MX" "https://en.wikipedia.org/wiki/Mexico" 127575529 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Flag_of_Mexico.svg/23px-Flag_of_Mexico.svg.png") "Americas" "Central America" - , Country "Japan" "JP" "https://en.wikipedia.org/wiki/Japan" 126860301 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/9/9e/Flag_of_Japan.svg/23px-Flag_of_Japan.svg.png") "Asia" "Eastern Asia" - , Country "Ethiopia" "ET" "https://en.wikipedia.org/wiki/Ethiopia" 112078730 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Flag_of_Ethiopia.svg/23px-Flag_of_Ethiopia.svg.png") "Africa" "Eastern Africa" - , Country "Philippines" "PH" "https://en.wikipedia.org/wiki/Philippines" 108116615 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Flag_of_the_Philippines.svg/23px-Flag_of_the_Philippines.svg.png") "Asia" "South-eastern Asia" - , Country "Egypt" "EG" "https://en.wikipedia.org/wiki/Egypt" 100388073 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Flag_of_Egypt.svg/23px-Flag_of_Egypt.svg.png") "Africa" "Northern Africa" - , Country "Vietnam" "VN" "https://en.wikipedia.org/wiki/Vietnam" 96462106 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Flag_of_Vietnam.svg/23px-Flag_of_Vietnam.svg.png") "Asia" "South-eastern Asia" - , Country "DR Congo" "CD" "https://en.wikipedia.org/wiki/Democratic_Republic_of_the_Congo" 86790567 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Flag_of_the_Democratic_Republic_of_the_Congo.svg/20px-Flag_of_the_Democratic_Republic_of_the_Congo.svg.png") "Africa" "Middle Africa" - , Country "Germany" "DE" "https://en.wikipedia.org/wiki/Germany" 83517045 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/b/ba/Flag_of_Germany.svg/23px-Flag_of_Germany.svg.png") "Europe" "Western Europe" - , Country "Turkey" "TR" "https://en.wikipedia.org/wiki/Turkey" 83429615 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/Flag_of_Turkey.svg/23px-Flag_of_Turkey.svg.png") "Asia" "Western Asia" - , Country "Iran" "IR" "https://en.wikipedia.org/wiki/Iran" 82913906 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ca/Flag_of_Iran.svg/23px-Flag_of_Iran.svg.png") "Asia" "Southern Asia" - , Country "Thailand" "TH" "https://en.wikipedia.org/wiki/Thailand" 69037513 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Flag_of_Thailand.svg/23px-Flag_of_Thailand.svg.png") "Asia" "South-eastern Asia" - , Country "United Kingdom" "UK" "https://en.wikipedia.org/wiki/United_Kingdom" 67530172 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/a/ae/Flag_of_the_United_Kingdom.svg/23px-Flag_of_the_United_Kingdom.svg.png") "Europe" "Northern Europe" - , Country "France" "FR" "https://en.wikipedia.org/wiki/France" 65129728 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/c/c3/Flag_of_France.svg/23px-Flag_of_France.svg.png") "Europe" "Western Europe" - , Country "Italy" "IT" "https://en.wikipedia.org/wiki/Italy" 60550075 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/0/03/Flag_of_Italy.svg/23px-Flag_of_Italy.svg.png") "Europe" "Southern Europe" - , Country "South Africa" "ZA" "https://en.wikipedia.org/wiki/South_Africa" 58558270 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Flag_of_South_Africa.svg/23px-Flag_of_South_Africa.svg.png") "Africa" "Southern Africa" - , Country "Tanzania" "TZ" "https://en.wikipedia.org/wiki/Tanzania" 58005463 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Flag_of_Tanzania.svg/23px-Flag_of_Tanzania.svg.png") "Africa" "Eastern Africa" - , Country "Myanmar" "MM" "https://en.wikipedia.org/wiki/Myanmar" 54045420 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8c/Flag_of_Myanmar.svg/23px-Flag_of_Myanmar.svg.png") "Asia" "South-eastern Asia" - , Country "Kenya" "KE" "https://en.wikipedia.org/wiki/Kenya" 52573973 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Flag_of_Kenya.svg/23px-Flag_of_Kenya.svg.png") "Africa" "Eastern Africa" - , Country "South Korea" "KR" "https://en.wikipedia.org/wiki/South_Korea" 51225308 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/09/Flag_of_South_Korea.svg/23px-Flag_of_South_Korea.svg.png") "Asia" "Eastern Asia" - , Country "Colombia" "CO" "https://en.wikipedia.org/wiki/Colombia" 50339443 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Flag_of_Colombia.svg/23px-Flag_of_Colombia.svg.png") "Americas" "South America" - , Country "Spain" "ES" "https://en.wikipedia.org/wiki/Spain" 46736776 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/9/9a/Flag_of_Spain.svg/23px-Flag_of_Spain.svg.png") "Europe" "Southern Europe" - , Country "Argentina" "AR" "https://en.wikipedia.org/wiki/Argentina" 44780677 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Flag_of_Argentina.svg/23px-Flag_of_Argentina.svg.png") "Americas" "South America" - , Country "Uganda" "UG" "https://en.wikipedia.org/wiki/Uganda" 44269594 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Flag_of_Uganda.svg/23px-Flag_of_Uganda.svg.png") "Africa" "Eastern Africa" - , Country "Ukraine" "UA" "https://en.wikipedia.org/wiki/Ukraine" 43993638 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Flag_of_Ukraine.svg/23px-Flag_of_Ukraine.svg.png") "Europe" "Eastern Europe" - , Country "Algeria" "DZ" "https://en.wikipedia.org/wiki/Algeria" 43053054 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Flag_of_Algeria.svg/23px-Flag_of_Algeria.svg.png") "Africa" "Northern Africa" - , Country "Sudan" "SD" "https://en.wikipedia.org/wiki/Sudan" 42813238 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/Flag_of_Sudan.svg/23px-Flag_of_Sudan.svg.png") "Africa" "Northern Africa" - , Country "Iraq" "IQ" "https://en.wikipedia.org/wiki/Iraq" 39309783 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Flag_of_Iraq.svg/23px-Flag_of_Iraq.svg.png") "Asia" "Western Asia" - , Country "Afghanistan" "AF" "https://en.wikipedia.org/wiki/Afghanistan" 38041754 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5c/Flag_of_the_Taliban.svg/23px-Flag_of_the_Taliban.svg.png") "Asia" "Southern Asia" - , Country "Poland" "PL" "https://en.wikipedia.org/wiki/Poland" 37887768 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/1/12/Flag_of_Poland.svg/23px-Flag_of_Poland.svg.png") "Europe" "Eastern Europe" - , Country "Canada" "CA" "https://en.wikipedia.org/wiki/Canada" 37411047 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Flag_of_Canada_%28Pantone%29.svg/23px-Flag_of_Canada_%28Pantone%29.svg.png") "Americas" "Northern America" - , Country "Morocco" "MA" "https://en.wikipedia.org/wiki/Morocco" 36471769 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Flag_of_Morocco.svg/23px-Flag_of_Morocco.svg.png") "Africa" "Northern Africa" - , Country "Saudi Arabia" "SA" "https://en.wikipedia.org/wiki/Saudi_Arabia" 34268528 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Flag_of_Saudi_Arabia.svg/23px-Flag_of_Saudi_Arabia.svg.png") "Asia" "Western Asia" - , Country "Uzbekistan" "UZ" "https://en.wikipedia.org/wiki/Uzbekistan" 32981716 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Flag_of_Uzbekistan.svg/23px-Flag_of_Uzbekistan.svg.png") "Asia" "Central Asia" - , Country "Peru" "PE" "https://en.wikipedia.org/wiki/Peru" 32510453 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Flag_of_Peru.svg/23px-Flag_of_Peru.svg.png") "Americas" "South America" - , Country "Malaysia" "MY" "https://en.wikipedia.org/wiki/Malaysia" 31949777 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Flag_of_Malaysia.svg/23px-Flag_of_Malaysia.svg.png") "Asia" "South-eastern Asia" - , Country "Angola" "AO" "https://en.wikipedia.org/wiki/Angola" 31825295 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Flag_of_Angola.svg/23px-Flag_of_Angola.svg.png") "Africa" "Middle Africa" - , Country "Mozambique" "MZ" "https://en.wikipedia.org/wiki/Mozambique" 30366036 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/Flag_of_Mozambique.svg/23px-Flag_of_Mozambique.svg.png") "Africa" "Eastern Africa" - , Country "Yemen" "YE" "https://en.wikipedia.org/wiki/Yemen" 29161922 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/89/Flag_of_Yemen.svg/23px-Flag_of_Yemen.svg.png") "Asia" "Western Asia" - , Country "Ghana" "GH" "https://en.wikipedia.org/wiki/Ghana" 28833629 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Flag_of_Ghana.svg/23px-Flag_of_Ghana.svg.png") "Africa" "Western Africa" - , Country "Nepal" "NP" "https://en.wikipedia.org/wiki/Nepal" 28608710 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9b/Flag_of_Nepal.svg/16px-Flag_of_Nepal.svg.png") "Asia" "Southern Asia" - , Country "Venezuela" "VE" "https://en.wikipedia.org/wiki/Venezuela" 28515829 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Flag_of_Venezuela.svg/23px-Flag_of_Venezuela.svg.png") "Americas" "South America" - , Country "Madagascar" "MG" "https://en.wikipedia.org/wiki/Madagascar" 26969307 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Flag_of_Madagascar.svg/23px-Flag_of_Madagascar.svg.png") "Africa" "Eastern Africa" - , Country "North Korea" "KP" "https://en.wikipedia.org/wiki/North_Korea" 25666161 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/51/Flag_of_North_Korea.svg/23px-Flag_of_North_Korea.svg.png") "Asia" "Eastern Asia" - , Country "Ivory Coast" "CI" "https://en.wikipedia.org/wiki/Ivory_Coast" 25716544 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Flag_of_C%C3%B4te_d%27Ivoire.svg/23px-Flag_of_C%C3%B4te_d%27Ivoire.svg.png") "Africa" "Western Africa" - , Country "Cameroon" "CM" "https://en.wikipedia.org/wiki/Cameroon" 25876380 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Flag_of_Cameroon.svg/23px-Flag_of_Cameroon.svg.png") "Africa" "Middle Africa" - , Country "Australia" "AU" "https://en.wikipedia.org/wiki/Australia" 25203198 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/88/Flag_of_Australia_%28converted%29.svg/23px-Flag_of_Australia_%28converted%29.svg.png") "Oceania" "Australia and New Zealand" - , Country "Taiwan" "TW" "https://en.wikipedia.org/wiki/Taiwan" 23773876 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Flag_of_the_Republic_of_China.svg/23px-Flag_of_the_Republic_of_China.svg.png") "Asia" "Eastern Asia" - , Country "Niger" "NE" "https://en.wikipedia.org/wiki/Niger" 23310715 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/Flag_of_Niger.svg/18px-Flag_of_Niger.svg.png") "Africa" "Western Africa" - , Country "Sri Lanka" "LK" "https://en.wikipedia.org/wiki/Sri_Lanka" 21323733 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/Flag_of_Sri_Lanka.svg/23px-Flag_of_Sri_Lanka.svg.png") "Asia" "Southern Asia" - , Country "Burkina Faso" "BF" "https://en.wikipedia.org/wiki/Burkina_Faso" 20321378 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Flag_of_Burkina_Faso.svg/23px-Flag_of_Burkina_Faso.svg.png") "Africa" "Western Africa" - , Country "Mali" "ML" "https://en.wikipedia.org/wiki/Mali" 19658031 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/Flag_of_Mali.svg/23px-Flag_of_Mali.svg.png") "Africa" "Western Africa" - , Country "Romania" "RO" "https://en.wikipedia.org/wiki/Romania" 19364557 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Flag_of_Romania.svg/23px-Flag_of_Romania.svg.png") "Europe" "Eastern Europe" - , Country "Malawi" "MW" "https://en.wikipedia.org/wiki/Malawi" 18628747 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Flag_of_Malawi.svg/23px-Flag_of_Malawi.svg.png") "Africa" "Eastern Africa" - , Country "Chile" "CL" "https://en.wikipedia.org/wiki/Chile" 18952038 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Flag_of_Chile.svg/23px-Flag_of_Chile.svg.png") "Americas" "South America" - , Country "Kazakhstan" "KZ" "https://en.wikipedia.org/wiki/Kazakhstan" 18551427 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Flag_of_Kazakhstan.svg/23px-Flag_of_Kazakhstan.svg.png") "Asia" "Central Asia" - , Country "Zambia" "ZM" "https://en.wikipedia.org/wiki/Zambia" 17861030 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Flag_of_Zambia.svg/23px-Flag_of_Zambia.svg.png") "Africa" "Eastern Africa" - , Country "Guatemala" "GT" "https://en.wikipedia.org/wiki/Guatemala" 17581472 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Flag_of_Guatemala.svg/23px-Flag_of_Guatemala.svg.png") "Americas" "Central America" - , Country "Ecuador" "EC" "https://en.wikipedia.org/wiki/Ecuador" 17373662 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Flag_of_Ecuador.svg/23px-Flag_of_Ecuador.svg.png") "Americas" "South America" - , Country "Netherlands" "NL" "https://en.wikipedia.org/wiki/Netherlands" 17097130 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Flag_of_the_Netherlands.svg/23px-Flag_of_the_Netherlands.svg.png") "Europe" "Western Europe" - , Country "Syria" "SY" "https://en.wikipedia.org/wiki/Syria" 17070135 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Flag_of_Syria.svg/23px-Flag_of_Syria.svg.png") "Asia" "Western Asia" - , Country "Cambodia" "KH" "https://en.wikipedia.org/wiki/Cambodia" 16486542 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Flag_of_Cambodia.svg/23px-Flag_of_Cambodia.svg.png") "Asia" "South-eastern Asia" - , Country "Senegal" "SN" "https://en.wikipedia.org/wiki/Senegal" 16296364 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fd/Flag_of_Senegal.svg/23px-Flag_of_Senegal.svg.png") "Africa" "Western Africa" - , Country "Chad" "TD" "https://en.wikipedia.org/wiki/Chad" 15946876 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Flag_of_Chad.svg/23px-Flag_of_Chad.svg.png") "Africa" "Middle Africa" - , Country "Somalia" "SO" "https://en.wikipedia.org/wiki/Somalia" 15442905 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/Flag_of_Somalia.svg/23px-Flag_of_Somalia.svg.png") "Africa" "Eastern Africa" - , Country "Zimbabwe" "ZW" "https://en.wikipedia.org/wiki/Zimbabwe" 14645468 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Flag_of_Zimbabwe.svg/23px-Flag_of_Zimbabwe.svg.png") "Africa" "Eastern Africa" - , Country "Guinea" "GN" "https://en.wikipedia.org/wiki/Guinea" 12771246 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ed/Flag_of_Guinea.svg/23px-Flag_of_Guinea.svg.png") "Africa" "Western Africa" - , Country "Rwanda" "RW" "https://en.wikipedia.org/wiki/Rwanda" 12626950 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Flag_of_Rwanda.svg/23px-Flag_of_Rwanda.svg.png") "Africa" "Eastern Africa" - , Country "Benin" "BJ" "https://en.wikipedia.org/wiki/Benin" 11801151 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flag_of_Benin.svg/23px-Flag_of_Benin.svg.png") "Africa" "Western Africa" - , Country "Tunisia" "TN" "https://en.wikipedia.org/wiki/Tunisia" 11694719 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/Flag_of_Tunisia.svg/23px-Flag_of_Tunisia.svg.png") "Africa" "Northern Africa" - , Country "Belgium" "BE" "https://en.wikipedia.org/wiki/Belgium" 11539328 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/Flag_of_Belgium_%28civil%29.svg/23px-Flag_of_Belgium_%28civil%29.svg.png") "Europe" "Western Europe" - , Country "Bolivia" "BO" "https://en.wikipedia.org/wiki/Bolivia" 11513100 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b3/Bandera_de_Bolivia_%28Estado%29.svg/22px-Bandera_de_Bolivia_%28Estado%29.svg.png") "Americas" "South America" - , Country "Cuba" "CU" "https://en.wikipedia.org/wiki/Cuba" 11333483 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Flag_of_Cuba.svg/23px-Flag_of_Cuba.svg.png") "Americas" "Caribbean" - , Country "Haiti" "HT" "https://en.wikipedia.org/wiki/Haiti" 11263770 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Flag_of_Haiti.svg/23px-Flag_of_Haiti.svg.png") "Americas" "Caribbean" - , Country "South Sudan" "SS" "https://en.wikipedia.org/wiki/South_Sudan" 11062113 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/Flag_of_South_Sudan.svg/23px-Flag_of_South_Sudan.svg.png") "Africa" "Eastern Africa" - , Country "Burundi" "BI" "https://en.wikipedia.org/wiki/Burundi" 10864245 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Flag_of_Burundi.svg/23px-Flag_of_Burundi.svg.png") "Africa" "Eastern Africa" - , Country "Dominican Republic" "DO" "https://en.wikipedia.org/wiki/Dominican_Republic" 10738958 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/Flag_of_the_Dominican_Republic.svg/23px-Flag_of_the_Dominican_Republic.svg.png") "Americas" "Caribbean" - , Country "Czech Republic" "CZ" "https://en.wikipedia.org/wiki/Czech_Republic" 10689209 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Flag_of_the_Czech_Republic.svg/23px-Flag_of_the_Czech_Republic.svg.png") "Europe" "Eastern Europe" - , Country "Greece" "GR" "https://en.wikipedia.org/wiki/Greece" 10473455 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5c/Flag_of_Greece.svg/23px-Flag_of_Greece.svg.png") "Europe" "Southern Europe" - , Country "Portugal" "PT" "https://en.wikipedia.org/wiki/Portugal" 10226187 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5c/Flag_of_Portugal.svg/23px-Flag_of_Portugal.svg.png") "Europe" "Southern Europe" - , Country "Jordan" "JO" "https://en.wikipedia.org/wiki/Jordan" 10101694 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c0/Flag_of_Jordan.svg/23px-Flag_of_Jordan.svg.png") "Asia" "Western Asia" - , Country "Azerbaijan" "AZ" "https://en.wikipedia.org/wiki/Azerbaijan" 10047718 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Flag_of_Azerbaijan.svg/23px-Flag_of_Azerbaijan.svg.png") "Asia" "Western Asia" - , Country "Sweden" "SE" "https://en.wikipedia.org/wiki/Sweden" 10036379 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/4/4c/Flag_of_Sweden.svg/23px-Flag_of_Sweden.svg.png") "Europe" "Northern Europe" - , Country "United Arab Emirates" "AE" "https://en.wikipedia.org/wiki/United_Arab_Emirates" 9770529 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Flag_of_the_United_Arab_Emirates.svg/23px-Flag_of_the_United_Arab_Emirates.svg.png") "Asia" "Western Asia" - , Country "Honduras" "HN" "https://en.wikipedia.org/wiki/Honduras" 9746117 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Flag_of_Honduras.svg/23px-Flag_of_Honduras.svg.png") "Americas" "Central America" - , Country "Hungary" "HU" "https://en.wikipedia.org/wiki/Hungary" 9684679 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Flag_of_Hungary.svg/23px-Flag_of_Hungary.svg.png") "Europe" "Eastern Europe" - , Country "Belarus" "BY" "https://en.wikipedia.org/wiki/Belarus" 9452411 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Flag_of_Belarus.svg/23px-Flag_of_Belarus.svg.png") "Europe" "Eastern Europe" - , Country "Tajikistan" "TJ" "https://en.wikipedia.org/wiki/Tajikistan" 9321018 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/Flag_of_Tajikistan.svg/23px-Flag_of_Tajikistan.svg.png") "Asia" "Central Asia" - , Country "Austria" "AT" "https://en.wikipedia.org/wiki/Austria" 8955102 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Flag_of_Austria.svg/23px-Flag_of_Austria.svg.png") "Europe" "Western Europe" - , Country "Papua New Guinea" "PG" "https://en.wikipedia.org/wiki/Papua_New_Guinea" 8776109 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Flag_of_Papua_New_Guinea.svg/20px-Flag_of_Papua_New_Guinea.svg.png") "Oceania" "Melanesia" - , Country "Serbia" "RS" "https://en.wikipedia.org/wiki/Serbia" 8772235 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Flag_of_Serbia.svg/23px-Flag_of_Serbia.svg.png") "Europe" "Southern Europe" - , Country "Switzerland" "CH" "https://en.wikipedia.org/wiki/Switzerland" 8591365 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Flag_of_Switzerland.svg/16px-Flag_of_Switzerland.svg.png") "Europe" "Western Europe" - , Country "Israel" "IL" "https://en.wikipedia.org/wiki/Israel" 8519377 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d4/Flag_of_Israel.svg/21px-Flag_of_Israel.svg.png") "Asia" "Western Asia" - , Country "Togo" "TG" "https://en.wikipedia.org/wiki/Togo" 8082366 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Flag_of_Togo.svg/23px-Flag_of_Togo.svg.png") "Africa" "Western Africa" - , Country "Sierra Leone" "SL" "https://en.wikipedia.org/wiki/Sierra_Leone" 7813215 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Flag_of_Sierra_Leone.svg/23px-Flag_of_Sierra_Leone.svg.png") "Africa" "Western Africa" - , Country "Hong Kong" "HK" "https://en.wikipedia.org/wiki/Hong_Kong" 7436154 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/Flag_of_Hong_Kong.svg/23px-Flag_of_Hong_Kong.svg.png") "Asia" "Eastern Asia" - , Country "Laos" "LA" "https://en.wikipedia.org/wiki/Laos" 7169455 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Flag_of_Laos.svg/23px-Flag_of_Laos.svg.png") "Asia" "South-eastern Asia" - , Country "Paraguay" "PY" "https://en.wikipedia.org/wiki/Paraguay" 7044636 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/Flag_of_Paraguay.svg/23px-Flag_of_Paraguay.svg.png") "Americas" "South America" - , Country "Bulgaria" "BG" "https://en.wikipedia.org/wiki/Bulgaria" 7000119 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Flag_of_Bulgaria.svg/23px-Flag_of_Bulgaria.svg.png") "Europe" "Eastern Europe" - , Country "Lebanon" "LB" "https://en.wikipedia.org/wiki/Lebanon" 6855713 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/Flag_of_Lebanon.svg/23px-Flag_of_Lebanon.svg.png") "Asia" "Western Asia" - , Country "Libya" "LY" "https://en.wikipedia.org/wiki/Libya" 6777452 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Flag_of_Libya.svg/23px-Flag_of_Libya.svg.png") "Africa" "Northern Africa" - , Country "Nicaragua" "NI" "https://en.wikipedia.org/wiki/Nicaragua" 6545502 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Flag_of_Nicaragua.svg/23px-Flag_of_Nicaragua.svg.png") "Americas" "Central America" - , Country "El Salvador" "SV" "https://en.wikipedia.org/wiki/El_Salvador" 6453553 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Flag_of_El_Salvador.svg/23px-Flag_of_El_Salvador.svg.png") "Americas" "Central America" - , Country "Kyrgyzstan" "KG" "https://en.wikipedia.org/wiki/Kyrgyzstan" 6415850 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Flag_of_Kyrgyzstan.svg/23px-Flag_of_Kyrgyzstan.svg.png") "Asia" "Central Asia" - , Country "Turkmenistan" "TM" "https://en.wikipedia.org/wiki/Turkmenistan" 5942089 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Flag_of_Turkmenistan.svg/23px-Flag_of_Turkmenistan.svg.png") "Asia" "Central Asia" - , Country "Singapore" "SG" "https://en.wikipedia.org/wiki/Singapore" 5804337 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/48/Flag_of_Singapore.svg/23px-Flag_of_Singapore.svg.png") "Asia" "South-eastern Asia" - , Country "Denmark" "DK" "https://en.wikipedia.org/wiki/Denmark" 5771876 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Flag_of_Denmark.svg/20px-Flag_of_Denmark.svg.png") "Europe" "Northern Europe" - , Country "Finland" "FI" "https://en.wikipedia.org/wiki/Finland" 5532156 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Flag_of_Finland.svg/23px-Flag_of_Finland.svg.png") "Europe" "Northern Europe" - , Country "Slovakia" "SK" "https://en.wikipedia.org/wiki/Slovakia" 5457013 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e6/Flag_of_Slovakia.svg/23px-Flag_of_Slovakia.svg.png") "Europe" "Eastern Europe" - , Country "Congo" "CG" "https://en.wikipedia.org/wiki/Republic_of_the_Congo" 5380508 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/Flag_of_the_Republic_of_the_Congo.svg/23px-Flag_of_the_Republic_of_the_Congo.svg.png") "Africa" "Middle Africa" - , Country "Norway" "NO" "https://en.wikipedia.org/wiki/Norway" 5378857 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Flag_of_Norway.svg/21px-Flag_of_Norway.svg.png") "Europe" "Northern Europe" - , Country "Costa Rica" "CR" "https://en.wikipedia.org/wiki/Costa_Rica" 5047561 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f2/Flag_of_Costa_Rica.svg/23px-Flag_of_Costa_Rica.svg.png") "Americas" "Central America" - , Country "Palestine" "PS" "https://en.wikipedia.org/wiki/State_of_Palestine" 4981420 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Flag_of_Palestine.svg/23px-Flag_of_Palestine.svg.png") "Asia" "Western Asia" - , Country "Oman" "OM" "https://en.wikipedia.org/wiki/Oman" 4974986 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Flag_of_Oman.svg/23px-Flag_of_Oman.svg.png") "Asia" "Western Asia" - , Country "Liberia" "LR" "https://en.wikipedia.org/wiki/Liberia" 4937374 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/Flag_of_Liberia.svg/23px-Flag_of_Liberia.svg.png") "Africa" "Western Africa" - , Country "Ireland" "IE" "https://en.wikipedia.org/wiki/Republic_of_Ireland" 4882495 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/45/Flag_of_Ireland.svg/23px-Flag_of_Ireland.svg.png") "Europe" "Northern Europe" - , Country "New Zealand" "NZ" "https://en.wikipedia.org/wiki/New_Zealand" 4783063 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Flag_of_New_Zealand.svg/23px-Flag_of_New_Zealand.svg.png") "Oceania" "Australia and New Zealand" - , Country "Central African Republic" "CF" "https://en.wikipedia.org/wiki/Central_African_Republic" 4745185 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Flag_of_the_Central_African_Republic.svg/23px-Flag_of_the_Central_African_Republic.svg.png") "Africa" "Middle Africa" - , Country "Mauritania" "MR" "https://en.wikipedia.org/wiki/Mauritania" 4525696 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Flag_of_Mauritania.svg/23px-Flag_of_Mauritania.svg.png") "Africa" "Western Africa" - , Country "Panama" "PA" "https://en.wikipedia.org/wiki/Panama" 4246439 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Flag_of_Panama.svg/23px-Flag_of_Panama.svg.png") "Americas" "Central America" - , Country "Kuwait" "KW" "https://en.wikipedia.org/wiki/Kuwait" 4207083 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Flag_of_Kuwait.svg/23px-Flag_of_Kuwait.svg.png") "Asia" "Western Asia" - , Country "Croatia" "HR" "https://en.wikipedia.org/wiki/Croatia" 4130304 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Flag_of_Croatia.svg/23px-Flag_of_Croatia.svg.png") "Europe" "Southern Europe" - , Country "Moldova" "MD" "https://en.wikipedia.org/wiki/Moldova" 4043263 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/Flag_of_Moldova.svg/23px-Flag_of_Moldova.svg.png") "Europe" "Eastern Europe" - , Country "Georgia" "GE" "https://en.wikipedia.org/wiki/Georgia_(country)" 3996765 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Flag_of_Georgia.svg/23px-Flag_of_Georgia.svg.png") "Asia" "Western Asia" - , Country "Eritrea" "ER" "https://en.wikipedia.org/wiki/Eritrea" 3497117 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Flag_of_Eritrea.svg/23px-Flag_of_Eritrea.svg.png") "Africa" "Eastern Africa" - , Country "Uruguay" "UY" "https://en.wikipedia.org/wiki/Uruguay" 3461734 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Flag_of_Uruguay.svg/23px-Flag_of_Uruguay.svg.png") "Americas" "South America" - , Country "Bosnia and Herzegovina" "BA" "https://en.wikipedia.org/wiki/Bosnia_and_Herzegovina" 3301000 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Flag_of_Bosnia_and_Herzegovina.svg/23px-Flag_of_Bosnia_and_Herzegovina.svg.png") "Europe" "Southern Europe" - , Country "Mongolia" "MN" "https://en.wikipedia.org/wiki/Mongolia" 3225167 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Flag_of_Mongolia.svg/23px-Flag_of_Mongolia.svg.png") "Asia" "Eastern Asia" - , Country "Armenia" "AM" "https://en.wikipedia.org/wiki/Armenia" 2957731 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Flag_of_Armenia.svg/23px-Flag_of_Armenia.svg.png") "Asia" "Western Asia" - , Country "Jamaica" "JM" "https://en.wikipedia.org/wiki/Jamaica" 2948279 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flag_of_Jamaica.svg/23px-Flag_of_Jamaica.svg.png") "Americas" "Caribbean" - , Country "Puerto Rico" "PR" "https://en.wikipedia.org/wiki/Puerto_Rico" 2933408 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/28/Flag_of_Puerto_Rico.svg/23px-Flag_of_Puerto_Rico.svg.png") "Americas" "Caribbean" - , Country "Albania" "AL" "https://en.wikipedia.org/wiki/Albania" 2880917 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Flag_of_Albania.svg/21px-Flag_of_Albania.svg.png") "Europe" "Southern Europe" - , Country "Qatar" "QA" "https://en.wikipedia.org/wiki/Qatar" 2832067 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Flag_of_Qatar.svg/23px-Flag_of_Qatar.svg.png") "Asia" "Western Asia" - , Country "Lithuania" "LT" "https://en.wikipedia.org/wiki/Lithuania" 2759627 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/Flag_of_Lithuania.svg/23px-Flag_of_Lithuania.svg.png") "Europe" "Northern Europe" - , Country "Namibia" "NA" "https://en.wikipedia.org/wiki/Namibia" 2494530 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Flag_of_Namibia.svg/23px-Flag_of_Namibia.svg.png") "Africa" "Southern Africa" - , Country "Gambia" "GM" "https://en.wikipedia.org/wiki/The_Gambia" 2347706 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Flag_of_The_Gambia.svg/23px-Flag_of_The_Gambia.svg.png") "Africa" "Western Africa" - , Country "Botswana" "BW" "https://en.wikipedia.org/wiki/Botswana" 2303697 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fa/Flag_of_Botswana.svg/23px-Flag_of_Botswana.svg.png") "Africa" "Southern Africa" - , Country "Gabon" "GA" "https://en.wikipedia.org/wiki/Gabon" 2172579 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/Flag_of_Gabon.svg/20px-Flag_of_Gabon.svg.png") "Africa" "Middle Africa" - , Country "Lesotho" "LS" "https://en.wikipedia.org/wiki/Lesotho" 2125268 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Flag_of_Lesotho.svg/23px-Flag_of_Lesotho.svg.png") "Africa" "Southern Africa" - , Country "North Macedonia" "MK" "https://en.wikipedia.org/wiki/North_Macedonia" 2083459 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Flag_of_North_Macedonia.svg/23px-Flag_of_North_Macedonia.svg.png") "Europe" "Southern Europe" - , Country "Slovenia" "SI" "https://en.wikipedia.org/wiki/Slovenia" 2078654 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Flag_of_Slovenia.svg/23px-Flag_of_Slovenia.svg.png") "Europe" "Southern Europe" - , Country "Guinea-Bissau" "GW" "https://en.wikipedia.org/wiki/Guinea-Bissau" 1920922 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/Flag_of_Guinea-Bissau.svg/23px-Flag_of_Guinea-Bissau.svg.png") "Africa" "Western Africa" - , Country "Latvia" "LV" "https://en.wikipedia.org/wiki/Latvia" 1906743 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Flag_of_Latvia.svg/23px-Flag_of_Latvia.svg.png") "Europe" "Northern Europe" - , Country "Bahrain" "BH" "https://en.wikipedia.org/wiki/Bahrain" 1641172 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Flag_of_Bahrain.svg/23px-Flag_of_Bahrain.svg.png") "Asia" "Western Asia" - , Country "Trinidad and Tobago" "TT" "https://en.wikipedia.org/wiki/Trinidad_and_Tobago" 1394973 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/64/Flag_of_Trinidad_and_Tobago.svg/23px-Flag_of_Trinidad_and_Tobago.svg.png") "Americas" "Caribbean" - , Country "Equatorial Guinea" "GQ" "https://en.wikipedia.org/wiki/Equatorial_Guinea" 1355986 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Flag_of_Equatorial_Guinea.svg/23px-Flag_of_Equatorial_Guinea.svg.png") "Africa" "Middle Africa" - , Country "Estonia" "EE" "https://en.wikipedia.org/wiki/Estonia" 1325648 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8f/Flag_of_Estonia.svg/23px-Flag_of_Estonia.svg.png") "Europe" "Northern Europe" - , Country "East Timor" "TL" "https://en.wikipedia.org/wiki/East_Timor" 1293119 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Flag_of_East_Timor.svg/23px-Flag_of_East_Timor.svg.png") "Asia" "South-eastern Asia" - , Country "Mauritius" "MU" "https://en.wikipedia.org/wiki/Mauritius" 1198575 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Flag_of_Mauritius.svg/23px-Flag_of_Mauritius.svg.png") "Africa" "Eastern Africa" - , Country "Cyprus" "CY" "https://en.wikipedia.org/wiki/Cyprus" 1179551 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d4/Flag_of_Cyprus.svg/23px-Flag_of_Cyprus.svg.png") "Asia" "Western Asia" - , Country "Eswatini" "SZ" "https://en.wikipedia.org/wiki/Eswatini" 1148130 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/Flag_of_Eswatini.svg/23px-Flag_of_Eswatini.svg.png") "Africa" "Southern Africa" - , Country "Djibouti" "DJ" "https://en.wikipedia.org/wiki/Djibouti" 973560 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Flag_of_Djibouti.svg/23px-Flag_of_Djibouti.svg.png") "Africa" "Eastern Africa" - , Country "Fiji" "FJ" "https://en.wikipedia.org/wiki/Fiji" 889953 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Flag_of_Fiji.svg/23px-Flag_of_Fiji.svg.png") "Oceania" "Melanesia" - , Country "Réunion" "RE" "https://en.wikipedia.org/wiki/R%C3%A9union" 888927 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8e/Proposed_flag_of_R%C3%A9union_%28VAR%29.svg/23px-Proposed_flag_of_R%C3%A9union_%28VAR%29.svg.png") "Africa" "Eastern Africa" - , Country "Comoros" "KM" "https://en.wikipedia.org/wiki/Comoros" 850886 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/94/Flag_of_the_Comoros.svg/23px-Flag_of_the_Comoros.svg.png") "Africa" "Eastern Africa" - , Country "Guyana" "GY" "https://en.wikipedia.org/wiki/Guyana" 782766 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Flag_of_Guyana.svg/23px-Flag_of_Guyana.svg.png") "Americas" "South America" - , Country "Bhutan" "BT" "https://en.wikipedia.org/wiki/Bhutan" 763092 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Flag_of_Bhutan.svg/23px-Flag_of_Bhutan.svg.png") "Asia" "Southern Asia" - , Country "Solomon Islands" "SB" "https://en.wikipedia.org/wiki/Solomon_Islands" 669823 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Flag_of_the_Solomon_Islands.svg/23px-Flag_of_the_Solomon_Islands.svg.png") "Oceania" "Melanesia" - , Country "Macau" "MO" "https://en.wikipedia.org/wiki/Macau" 640445 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/63/Flag_of_Macau.svg/23px-Flag_of_Macau.svg.png") "Asia" "Eastern Asia" - , Country "Montenegro" "ME" "https://en.wikipedia.org/wiki/Montenegro" 627987 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/64/Flag_of_Montenegro.svg/23px-Flag_of_Montenegro.svg.png") "Europe" "Southern Europe" - , Country "Luxembourg" "LU" "https://en.wikipedia.org/wiki/Luxembourg" 615729 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Flag_of_Luxembourg.svg/23px-Flag_of_Luxembourg.svg.png") "Europe" "Western Europe" - , Country "Western Sahara" "EH" "https://en.wikipedia.org/wiki/Western_Sahara" 582463 Nothing "Africa" "Northern Africa" - , Country "Suriname" "SR" "https://en.wikipedia.org/wiki/Suriname" 581372 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Flag_of_Suriname.svg/23px-Flag_of_Suriname.svg.png") "Americas" "South America" - , Country "Cape Verde" "CV" "https://en.wikipedia.org/wiki/Cape_Verde" 549935 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Flag_of_Cape_Verde.svg/23px-Flag_of_Cape_Verde.svg.png") "Africa" "Western Africa" - , Country "Maldives" "MV" "https://en.wikipedia.org/wiki/Maldives" 530953 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Flag_of_Maldives.svg/23px-Flag_of_Maldives.svg.png") "Asia" "Southern Asia" - , Country "Guadeloupe" "GP" "https://en.wikipedia.org/wiki/Guadeloupe" 447905 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Unofficial_flag_of_Guadeloupe_%28local%29.svg/23px-Unofficial_flag_of_Guadeloupe_%28local%29.svg.png") "Americas" "Caribbean" - , Country "Malta" "MT" "https://en.wikipedia.org/wiki/Malta" 440372 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Flag_of_Malta.svg/23px-Flag_of_Malta.svg.png") "Europe" "Southern Europe" - , Country "Brunei" "BN" "https://en.wikipedia.org/wiki/Brunei" 433285 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Flag_of_Brunei.svg/23px-Flag_of_Brunei.svg.png") "Asia" "South-eastern Asia" - , Country "Belize" "BZ" "https://en.wikipedia.org/wiki/Belize" 390353 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Flag_of_Belize.svg/23px-Flag_of_Belize.svg.png") "Americas" "Central America" - , Country "Bahamas" "BS" "https://en.wikipedia.org/wiki/The_Bahamas" 389482 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Flag_of_the_Bahamas.svg/23px-Flag_of_the_Bahamas.svg.png") "Americas" "Caribbean" - , Country "Martinique" "MQ" "https://en.wikipedia.org/wiki/Martinique" 375554 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/64/Snake_Flag_of_Martinique.svg/23px-Snake_Flag_of_Martinique.svg.png") "Americas" "Caribbean" - , Country "Iceland" "IS" "https://en.wikipedia.org/wiki/Iceland" 339031 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/Flag_of_Iceland.svg/21px-Flag_of_Iceland.svg.png") "Europe" "Northern Europe" - , Country "Vanuatu" "VU" "https://en.wikipedia.org/wiki/Vanuatu" 299882 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Flag_of_Vanuatu.svg/23px-Flag_of_Vanuatu.svg.png") "Oceania" "Melanesia" - , Country "Barbados" "BB" "https://en.wikipedia.org/wiki/Barbados" 287025 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Flag_of_Barbados.svg/23px-Flag_of_Barbados.svg.png") "Americas" "Caribbean" - , Country "New Caledonia" "NC" "https://en.wikipedia.org/wiki/New_Caledonia" 282750 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Flag_of_FLNKS.svg/23px-Flag_of_FLNKS.svg.png") "Oceania" "Melanesia" - , Country "French Guiana" "GF" "https://en.wikipedia.org/wiki/French_Guiana" 282731 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Flag_of_French_Guiana.svg/23px-Flag_of_French_Guiana.svg.png") "Americas" "South America" - , Country "French Polynesia" "PF" "https://en.wikipedia.org/wiki/French_Polynesia" 279287 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Flag_of_French_Polynesia.svg/23px-Flag_of_French_Polynesia.svg.png") "Oceania" "Polynesia" - , Country "Mayotte" "YT" "https://en.wikipedia.org/wiki/Mayotte" 266150 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Flag_of_Mayotte_%28Local%29.svg/23px-Flag_of_Mayotte_%28Local%29.svg.png") "Africa" "Eastern Africa" - , Country "São Tomé and Príncipe" "ST" "https://en.wikipedia.org/wiki/S%C3%A3o_Tom%C3%A9_and_Pr%C3%ADncipe" 215056 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Flag_of_Sao_Tome_and_Principe.svg/23px-Flag_of_Sao_Tome_and_Principe.svg.png") "Africa" "Middle Africa" - , Country "Samoa" "WS" "https://en.wikipedia.org/wiki/Samoa" 197097 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Flag_of_Samoa.svg/23px-Flag_of_Samoa.svg.png") "Oceania" "Polynesia" - , Country "Saint Lucia" "LC" "https://en.wikipedia.org/wiki/Saint_Lucia" 182790 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/Flag_of_Saint_Lucia.svg/23px-Flag_of_Saint_Lucia.svg.png") "Americas" "Caribbean" - , Country "Channel Islands" "GB" "https://en.wikipedia.org/wiki/Channel_Islands" 172259 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/a/ae/Flag_of_the_United_Kingdom.svg/23px-Flag_of_the_United_Kingdom.svg.png") "Europe" "Northern Europe" - , Country "Guam" "GU" "https://en.wikipedia.org/wiki/Guam" 167294 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Flag_of_Guam.svg/23px-Flag_of_Guam.svg.png") "Oceania" "Micronesia" - , Country "Curaçao" "CW" "https://en.wikipedia.org/wiki/Cura%C3%A7ao" 163424 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Flag_of_Cura%C3%A7ao.svg/23px-Flag_of_Cura%C3%A7ao.svg.png") "Americas" "Caribbean" - , Country "Kiribati" "KI" "https://en.wikipedia.org/wiki/Kiribati" 117606 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Flag_of_Kiribati.svg/23px-Flag_of_Kiribati.svg.png") "Oceania" "Micronesia" - , Country "Micronesia" "FM" "https://en.wikipedia.org/wiki/Federated_States_of_Micronesia" 113815 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Flag_of_the_Federated_States_of_Micronesia.svg/23px-Flag_of_the_Federated_States_of_Micronesia.svg.png") "Oceania" "Micronesia" - , Country "Grenada" "GD" "https://en.wikipedia.org/wiki/Grenada" 112003 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Flag_of_Grenada.svg/23px-Flag_of_Grenada.svg.png") "Americas" "Caribbean" - , Country "Tonga" "TO" "https://en.wikipedia.org/wiki/Tonga" 110940 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Flag_of_Tonga.svg/23px-Flag_of_Tonga.svg.png") "Oceania" "Polynesia" - , Country "Saint Vincent and the Grenadines" "VC" "https://en.wikipedia.org/wiki/Saint_Vincent_and_the_Grenadines" 110589 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Flag_of_Saint_Vincent_and_the_Grenadines.svg/23px-Flag_of_Saint_Vincent_and_the_Grenadines.svg.png") "Americas" "Caribbean" - , Country "Aruba" "AW" "https://en.wikipedia.org/wiki/Aruba" 106314 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Flag_of_Aruba.svg/23px-Flag_of_Aruba.svg.png") "Americas" "Caribbean" - , Country "U.S. Virgin Islands" "VI" "https://en.wikipedia.org/wiki/United_States_Virgin_Islands" 104578 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Flag_of_the_United_States_Virgin_Islands.svg/23px-Flag_of_the_United_States_Virgin_Islands.svg.png") "Americas" "Caribbean" - , Country "Seychelles" "SC" "https://en.wikipedia.org/wiki/Seychelles" 97739 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Flag_of_Seychelles.svg/23px-Flag_of_Seychelles.svg.png") "Africa" "Eastern Africa" - , Country "Antigua and Barbuda" "AG" "https://en.wikipedia.org/wiki/Antigua_and_Barbuda" 97118 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/89/Flag_of_Antigua_and_Barbuda.svg/23px-Flag_of_Antigua_and_Barbuda.svg.png") "Americas" "Caribbean" - , Country "Isle of Man" "IM" "https://en.wikipedia.org/wiki/Isle_of_Man" 84584 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/Flag_of_the_Isle_of_Man.svg/23px-Flag_of_the_Isle_of_Man.svg.png") "Europe" "Northern Europe" - , Country "Andorra" "AD" "https://en.wikipedia.org/wiki/Andorra" 77142 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Flag_of_Andorra.svg/22px-Flag_of_Andorra.svg.png") "Europe" "Southern Europe" - , Country "Dominica" "DM" "https://en.wikipedia.org/wiki/Dominica" 71808 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c4/Flag_of_Dominica.svg/23px-Flag_of_Dominica.svg.png") "Americas" "Caribbean" - , Country "Cayman Islands" "KY" "https://en.wikipedia.org/wiki/Cayman_Islands" 64948 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Flag_of_the_Cayman_Islands.svg/23px-Flag_of_the_Cayman_Islands.svg.png") "Americas" "Caribbean" - , Country "Bermuda" "BM" "https://en.wikipedia.org/wiki/Bermuda" 62506 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Flag_of_Bermuda.svg/23px-Flag_of_Bermuda.svg.png") "Americas" "Northern America" - , Country "Marshall Islands" "MH" "https://en.wikipedia.org/wiki/Marshall_Islands" 58791 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Flag_of_the_Marshall_Islands.svg/23px-Flag_of_the_Marshall_Islands.svg.png") "Oceania" "Micronesia" - , Country "Greenland" "GL" "https://en.wikipedia.org/wiki/Greenland" 56672 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/09/Flag_of_Greenland.svg/23px-Flag_of_Greenland.svg.png") "Americas" "Northern America" - , Country "Northern Mariana Islands" "MP" "https://en.wikipedia.org/wiki/Northern_Mariana_Islands" 56188 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Flag_of_the_Northern_Mariana_Islands.svg/23px-Flag_of_the_Northern_Mariana_Islands.svg.png") "Oceania" "Micronesia" - , Country "American Samoa" "AS" "https://en.wikipedia.org/wiki/American_Samoa" 55312 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/Flag_of_American_Samoa.svg/23px-Flag_of_American_Samoa.svg.png") "Oceania" "Polynesia" - , Country "Saint Kitts and Nevis" "KN" "https://en.wikipedia.org/wiki/Saint_Kitts_and_Nevis" 52823 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Flag_of_Saint_Kitts_and_Nevis.svg/23px-Flag_of_Saint_Kitts_and_Nevis.svg.png") "Americas" "Caribbean" - , Country "Faroe Islands" "FO" "https://en.wikipedia.org/wiki/Faroe_Islands" 48678 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Flag_of_the_Faroe_Islands.svg/21px-Flag_of_the_Faroe_Islands.svg.png") "Europe" "Northern Europe" - , Country "Sint Maarten" "SX" "https://en.wikipedia.org/wiki/Sint_Maarten" 42388 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Flag_of_Sint_Maarten.svg/23px-Flag_of_Sint_Maarten.svg.png") "Americas" "Caribbean" - , Country "Monaco" "MC" "https://en.wikipedia.org/wiki/Monaco" 38964 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Flag_of_Monaco.svg/19px-Flag_of_Monaco.svg.png") "Europe" "Western Europe" - , Country "Turks and Caicos Islands" "TC" "https://en.wikipedia.org/wiki/Turks_and_Caicos_Islands" 38191 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/Flag_of_the_Turks_and_Caicos_Islands.svg/23px-Flag_of_the_Turks_and_Caicos_Islands.svg.png") "Americas" "Caribbean" - , Country "Liechtenstein" "LI" "https://en.wikipedia.org/wiki/Liechtenstein" 38019 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Flag_of_Liechtenstein.svg/23px-Flag_of_Liechtenstein.svg.png") "Europe" "Western Europe" - , Country "San Marino" "SM" "https://en.wikipedia.org/wiki/San_Marino" 33860 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Flag_of_San_Marino.svg/20px-Flag_of_San_Marino.svg.png") "Europe" "Southern Europe" - , Country "Gibraltar" "GI" "https://en.wikipedia.org/wiki/Gibraltar" 33701 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/Flag_of_Gibraltar.svg/23px-Flag_of_Gibraltar.svg.png") "Europe" "Southern Europe" - , Country "British Virgin Islands" "VG" "https://en.wikipedia.org/wiki/British_Virgin_Islands" 30030 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Flag_of_the_British_Virgin_Islands.svg/23px-Flag_of_the_British_Virgin_Islands.svg.png") "Americas" "Caribbean" - , Country "Caribbean Netherlands" "BQ" "https://en.wikipedia.org/wiki/Caribbean_Netherlands" 25979 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Flag_of_the_Netherlands.svg/23px-Flag_of_the_Netherlands.svg.png") "Americas" "Caribbean" - , Country "Palau" "PW" "https://en.wikipedia.org/wiki/Palau" 18008 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/4/48/Flag_of_Palau.svg/23px-Flag_of_Palau.svg.png") "Oceania" "Micronesia" - , Country "Cook Islands" "CK" "https://en.wikipedia.org/wiki/Cook_Islands" 17548 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Flag_of_the_Cook_Islands.svg/23px-Flag_of_the_Cook_Islands.svg.png") "Oceania" "Polynesia" - , Country "Anguilla" "AI" "https://en.wikipedia.org/wiki/Anguilla" 14869 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/Flag_of_Anguilla.svg/23px-Flag_of_Anguilla.svg.png") "Americas" "Caribbean" - , Country "Tuvalu" "TV" "https://en.wikipedia.org/wiki/Tuvalu" 11646 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Flag_of_Tuvalu.svg/23px-Flag_of_Tuvalu.svg.png") "Oceania" "Polynesia" - , Country "Wallis and Futuna" "WF" "https://en.wikipedia.org/wiki/Wallis_and_Futuna" 11432 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Flag_of_Wallis_and_Futuna.svg/23px-Flag_of_Wallis_and_Futuna.svg.png") "Oceania" "Polynesia" - , Country "Nauru" "NR" "https://en.wikipedia.org/wiki/Nauru" 10756 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Flag_of_Nauru.svg/23px-Flag_of_Nauru.svg.png") "Oceania" "Micronesia" - , Country "Saint Helena" "SH" "https://en.wikipedia.org/wiki/Saint_Helena,_Ascension_and_Tristan_da_Cunha" 6059 (Just "https://upload.wikimedia.org/wikipedia/en/thumb/a/ae/Flag_of_the_United_Kingdom.svg/23px-Flag_of_the_United_Kingdom.svg.png") "Africa" "Western Africa" - , Country "Saint Pierre and Miquelon" "PM" "https://en.wikipedia.org/wiki/Saint_Pierre_and_Miquelon" 5822 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Flag_of_Saint-Pierre_and_Miquelon.svg/23px-Flag_of_Saint-Pierre_and_Miquelon.svg.png") "Americas" "Northern America" - , Country "Montserrat" "MS" "https://en.wikipedia.org/wiki/Montserrat" 4989 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/Flag_of_Montserrat.svg/23px-Flag_of_Montserrat.svg.png") "Americas" "Caribbean" - , Country "Falkland Islands" "FK" "https://en.wikipedia.org/wiki/Falkland_Islands" 3377 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Flag_of_the_Falkland_Islands.svg/23px-Flag_of_the_Falkland_Islands.svg.png") "Americas" "South America" - , Country "Niue" "NU" "https://en.wikipedia.org/wiki/Niue" 1615 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/Flag_of_Niue.svg/23px-Flag_of_Niue.svg.png") "Oceania" "Polynesia" - , Country "Tokelau" "TK" "https://en.wikipedia.org/wiki/Tokelau" 1340 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8e/Flag_of_Tokelau.svg/23px-Flag_of_Tokelau.svg.png") "Oceania" "Polynesia" - , Country "Vatican City" "VA" "https://en.wikipedia.org/wiki/Vatican_City" 799 (Just "https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Flag_of_the_Vatican_City.svg/16px-Flag_of_the_Vatican_City.svg.png") "Europe" "Southern Europe" - ] - -awsmCss :: JSString -awsmCss = "\ - \@charset \"UTF-8\";\ - \/*!\ - \ * awsm.css v3.0.7 (https://igoradamenko.github.io/awsm.css/)\ - \ * Copyright 2015 Igor Adamenko (https://igoradamenko.com)\ - \ * Licensed under MIT (https://github.com/igoradamenko/awsm.css/blob/master/LICENSE.md)\ - \ */\ - \html{\ - \ font-family:system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"PT Sans\", \"Open Sans\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\ - \ font-size:100%;\ - \ line-height:1.4;\ - \ background:white;\ - \ color:black;\ - \ -webkit-overflow-scrolling:touch;\ - \}\ - \\ - \body{\ - \ margin:1.2em;\ - \ font-size:1rem;\ - \}\ - \@media (min-width: 20rem){\ - \ body{\ - \ font-size:calc(1rem + 0.00625 * (100vw - 20rem));\ - \ }\ - \}\ - \@media (min-width: 40rem){\ - \ body{\ - \ font-size:1.125rem;\ - \ }\ - \}\ - \body header,\ - \body main,\ - \body footer,\ - \body article{\ - \ position:relative;\ - \ max-width:40rem;\ - \ margin:0 auto;\ - \}\ - \body > header{\ - \ margin-bottom:3.5em;\ - \}\ - \body > header h1{\ - \ margin:0;\ - \ font-size:1.5em;\ - \}\ - \body > header p{\ - \ margin:0;\ - \ font-size:0.85em;\ - \}\ - \body > footer{\ - \ margin-top:6em;\ - \ padding-bottom:1.5em;\ - \ text-align:center;\ - \ font-size:0.8rem;\ - \ color:#aaaaaa;\ - \}\ - \\ - \nav{\ - \ margin:1em 0;\ - \}\ - \nav ul{\ - \ list-style:none;\ - \ margin:0;\ - \ padding:0;\ - \}\ - \nav li{\ - \ display:inline-block;\ - \ margin-right:1em;\ - \ margin-bottom:0.25em;\ - \}\ - \nav li:last-child{\ - \ margin-right:0;\ - \}\ - \nav a:visited{\ - \ color:#0064c1;\ - \}\ - \nav a:hover{\ - \ color:#f00000;\ - \}\ - \\ - \ul, ol{\ - \ margin-top:0;\ - \ padding-top:0;\ - \ padding-left:2.5em;\ - \}\ - \ul li + li, ol li + li{\ - \ margin-top:0.25em;\ - \}\ - \ul li > details, ol li > details{\ - \ margin:0;\ - \}\ - \\ - \p{\ - \ margin:1em 0;\ - \ -webkit-hyphens:auto;\ - \ -ms-hyphens:auto;\ - \ hyphens:auto;\ - \}\ - \p:first-child{\ - \ margin-top:0;\ - \}\ - \p:last-child{\ - \ margin-bottom:0;\ - \}\ - \p + ul, p + ol{\ - \ margin-top:-0.75em;\ - \}\ - \p img, p picture{\ - \ float:right;\ - \ margin-bottom:0.5em;\ - \ margin-left:0.5em;\ - \}\ - \p picture img{\ - \ float:none;\ - \ margin:0;\ - \}\ - \\ - \dd{\ - \ margin-bottom:1em;\ - \ margin-left:0;\ - \ padding-left:2.5em;\ - \}\ - \\ - \dt{\ - \ font-weight:700;\ - \}\ - \\ - \blockquote{\ - \ margin:0;\ - \ padding-left:2.5em;\ - \}\ - \\ - \aside{\ - \ margin:0.5em 0;\ - \ font-style:italic;\ - \ color:#aaaaaa;\ - \}\ - \@media (min-width: 65rem){\ - \ aside{\ - \ position:absolute;\ - \ right:-12.5rem;\ - \ width:9.375rem;\ - \ max-width:9.375rem;\ - \ margin:0;\ - \ padding-left:0.5em;\ - \ font-size:0.8em;\ - \ border-left:1px solid #f2f2f2;\ - \ }\ - \}\ - \aside:first-child{\ - \ margin-top:0;\ - \}\ - \aside:last-child{\ - \ margin-bottom:0;\ - \}\ - \\ - \section + section{\ - \ margin-top:2em;\ - \}\ - \\ - \h1, h2, h3, h4, h5, h6{\ - \ margin:1.25em 0 0;\ - \ line-height:1.2;\ - \}\ - \h1:hover > a[href^=\"#\"][id]:empty, h1:focus > a[href^=\"#\"][id]:empty, h2:hover > a[href^=\"#\"][id]:empty, h2:focus > a[href^=\"#\"][id]:empty, h3:hover > a[href^=\"#\"][id]:empty, h3:focus > a[href^=\"#\"][id]:empty, h4:hover > a[href^=\"#\"][id]:empty, h4:focus > a[href^=\"#\"][id]:empty, h5:hover > a[href^=\"#\"][id]:empty, h5:focus > a[href^=\"#\"][id]:empty, h6:hover > a[href^=\"#\"][id]:empty, h6:focus > a[href^=\"#\"][id]:empty{\ - \ opacity:1;\ - \}\ - \h1 + p, h1 + details, h2 + p, h2 + details, h3 + p, h3 + details, h4 + p, h4 + details, h5 + p, h5 + details, h6 + p, h6 + details{\ - \ margin-top:0.5em;\ - \}\ - \h1 > a[href^=\"#\"][id]:empty, h2 > a[href^=\"#\"][id]:empty, h3 > a[href^=\"#\"][id]:empty, h4 > a[href^=\"#\"][id]:empty, h5 > a[href^=\"#\"][id]:empty, h6 > a[href^=\"#\"][id]:empty{\ - \ position:absolute;\ - \ left:-0.65em;\ - \ opacity:0;\ - \ text-decoration:none;\ - \ font-weight:400;\ - \ line-height:1;\ - \ color:#aaaaaa;\ - \}\ - \@media (min-width: 40rem){\ - \ h1 > a[href^=\"#\"][id]:empty, h2 > a[href^=\"#\"][id]:empty, h3 > a[href^=\"#\"][id]:empty, h4 > a[href^=\"#\"][id]:empty, h5 > a[href^=\"#\"][id]:empty, h6 > a[href^=\"#\"][id]:empty{\ - \ left:-0.8em;\ - \ }\ - \}\ - \h1 > a[href^=\"#\"][id]:empty:target, h1 > a[href^=\"#\"][id]:empty:hover, h1 > a[href^=\"#\"][id]:empty:focus, h2 > a[href^=\"#\"][id]:empty:target, h2 > a[href^=\"#\"][id]:empty:hover, h2 > a[href^=\"#\"][id]:empty:focus, h3 > a[href^=\"#\"][id]:empty:target, h3 > a[href^=\"#\"][id]:empty:hover, h3 > a[href^=\"#\"][id]:empty:focus, h4 > a[href^=\"#\"][id]:empty:target, h4 > a[href^=\"#\"][id]:empty:hover, h4 > a[href^=\"#\"][id]:empty:focus, h5 > a[href^=\"#\"][id]:empty:target, h5 > a[href^=\"#\"][id]:empty:hover, h5 > a[href^=\"#\"][id]:empty:focus, h6 > a[href^=\"#\"][id]:empty:target, h6 > a[href^=\"#\"][id]:empty:hover, h6 > a[href^=\"#\"][id]:empty:focus{\ - \ opacity:1;\ - \ box-shadow:none;\ - \ color:black;\ - \}\ - \h1 > a[href^=\"#\"][id]:empty:target:focus, h2 > a[href^=\"#\"][id]:empty:target:focus, h3 > a[href^=\"#\"][id]:empty:target:focus, h4 > a[href^=\"#\"][id]:empty:target:focus, h5 > a[href^=\"#\"][id]:empty:target:focus, h6 > a[href^=\"#\"][id]:empty:target:focus{\ - \ outline:none;\ - \}\ - \h1 > a[href^=\"#\"][id]:empty::before, h2 > a[href^=\"#\"][id]:empty::before, h3 > a[href^=\"#\"][id]:empty::before, h4 > a[href^=\"#\"][id]:empty::before, h5 > a[href^=\"#\"][id]:empty::before, h6 > a[href^=\"#\"][id]:empty::before{\ - \ content:\"§ \";\ - \}\ - \\ - \h1{\ - \ font-size:2.5em;\ - \}\ - \\ - \h2{\ - \ font-size:1.75em;\ - \}\ - \\ - \h3{\ - \ font-size:1.25em;\ - \}\ - \\ - \h4{\ - \ font-size:1.15em;\ - \}\ - \\ - \h5{\ - \ font-size:1em;\ - \}\ - \\ - \h6{\ - \ margin-top:1em;\ - \ font-size:1em;\ - \ color:#aaaaaa;\ - \}\ - \\ - \article + article{\ - \ margin-top:4em;\ - \}\ - \article header p{\ - \ font-size:0.6em;\ - \ color:#aaaaaa;\ - \}\ - \article header p + h1, article header p + h2{\ - \ margin-top:-0.25em;\ - \}\ - \article header h1 + p, article header h2 + p{\ - \ margin-top:0.25em;\ - \}\ - \article header h1 a, article header h2 a{\ - \ color:black;\ - \}\ - \article header h1 a:visited, article header h2 a:visited{\ - \ color:#aaaaaa;\ - \}\ - \article header h1 a:visited:hover, article header h2 a:visited:hover{\ - \ color:#f00000;\ - \}\ - \article > footer{\ - \ margin-top:1.5em;\ - \ font-size:0.85em;\ - \}\ - \\ - \a{\ - \ color:#0064c1;\ - \}\ - \a:visited{\ - \ color:#8d39d0;\ - \}\ - \a:hover, a:active{\ - \ outline-width:0;\ - \}\ - \a:hover{\ - \ color:#f00000;\ - \}\ - \a abbr{\ - \ font-size:1em;\ - \}\ - \\ - \abbr{\ - \ margin-right:-0.075em;\ - \ text-decoration:none;\ - \ -webkit-hyphens:none;\ - \ -ms-hyphens:none;\ - \ hyphens:none;\ - \ letter-spacing:0.075em;\ - \ font-size:0.9em;\ - \}\ - \\ - \img, picture{\ - \ display:block;\ - \ max-width:100%;\ - \ margin:0 auto;\ - \}\ - \\ - \audio, video{\ - \ width:100%;\ - \ max-width:100%;\ - \}\ - \\ - \figure{\ - \ margin:1em 0 0.5em;\ - \ padding:0;\ - \}\ - \figure + p{\ - \ margin-top:0.5em;\ - \}\ - \figure figcaption{\ - \ opacity:0.65;\ - \ font-size:0.85em;\ - \}\ - \\ - \table{\ - \ display:inline-block;\ - \ border-spacing:0;\ - \ border-collapse:collapse;\ - \ overflow-x:auto;\ - \ max-width:100%;\ - \ text-align:left;\ - \ vertical-align:top;\ - \ background:linear-gradient(rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%) 0 0, linear-gradient(rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%) 100% 0;\ - \ background-attachment:scroll, scroll;\ - \ background-size:1px 100%, 1px 100%;\ - \ background-repeat:no-repeat, no-repeat;\ - \}\ - \table caption{\ - \ font-size:0.9em;\ - \ background:white;\ - \}\ - \table td, table th{\ - \ padding:0.35em 0.75em;\ - \ vertical-align:top;\ - \ font-size:0.9em;\ - \ border:1px solid #f2f2f2;\ - \ border-top:0;\ - \ border-left:0;\ - \}\ - \table td:first-child, table th:first-child{\ - \ padding-left:0;\ - \ background-image:linear-gradient(to right, white 50%, rgba(255, 255, 255, 0) 100%);\ - \ background-size:2px 100%;\ - \ background-repeat:no-repeat;\ - \}\ - \table td:last-child, table th:last-child{\ - \ padding-right:0;\ - \ border-right:0;\ - \ background-image:linear-gradient(to left, white 50%, rgba(255, 255, 255, 0) 100%);\ - \ background-position:100% 0;\ - \ background-size:2px 100%;\ - \ background-repeat:no-repeat;\ - \}\ - \table td:only-child, table th:only-child{\ - \ background-image:linear-gradient(to right, white 50%, rgba(255, 255, 255, 0) 100%), linear-gradient(to left, white 50%, rgba(255, 255, 255, 0) 100%);\ - \ background-position:0 0, 100% 0;\ - \ background-size:2px 100%, 2px 100%;\ - \ background-repeat:no-repeat, no-repeat;\ - \}\ - \table th{\ - \ line-height:1.2;\ - \}\ - \\ - \form{\ - \ margin-right:auto;\ - \ margin-left:auto;\ - \}\ - \@media (min-width: 40rem){\ - \ form{\ - \ max-width:80%;\ - \ }\ - \}\ - \form select, form label{\ - \ display:block;\ - \}\ - \form label:not(:first-child){\ - \ margin-top:1em;\ - \}\ - \form p label{\ - \ display:inline;\ - \}\ - \form p label + label{\ - \ margin-left:1em;\ - \}\ - \form legend:first-child + label{\ - \ margin-top:0;\ - \}\ - \form select, form input[type], form textarea{\ - \ margin-bottom:1em;\ - \}\ - \form input[type=checkbox], form input[type=radio]{\ - \ margin-bottom:0;\ - \}\ - \\ - \fieldset{\ - \ margin:0;\ - \ padding:0.5em 1em;\ - \ border:1px solid #aaaaaa;\ - \}\ - \\ - \legend{\ - \ color:#aaaaaa;\ - \}\ - \\ - \button{\ - \ outline:none;\ - \ box-sizing:border-box;\ - \ height:2em;\ - \ margin:0;\ - \ padding:calc(.25em - 1px) 0.5em;\ - \ font-family:inherit;\ - \ font-size:1em;\ - \ border:1px solid #aaaaaa;\ - \ border-radius:2px;\ - \ background:white;\ - \ color:black;\ - \ display:inline-block;\ - \ width:auto;\ - \ background:#f2f2f2;\ - \ color:black;\ - \ cursor:pointer;\ - \}\ - \button:focus{\ - \ border:1px solid black;\ - \}\ - \button:not([disabled]):hover{\ - \ border:1px solid black;\ - \}\ - \button:active{\ - \ background-color:#aaaaaa;\ - \}\ - \button[disabled]{\ - \ color:#aaaaaa;\ - \ cursor:not-allowed;\ - \}\ - \\ - \select{\ - \ outline:none;\ - \ box-sizing:border-box;\ - \ height:2em;\ - \ margin:0;\ - \ padding:calc(.25em - 1px) 0.5em;\ - \ font-family:inherit;\ - \ font-size:1em;\ - \ border:1px solid #aaaaaa;\ - \ border-radius:2px;\ - \ background:white;\ - \ color:black;\ - \ display:inline-block;\ - \ width:auto;\ - \ background:#f2f2f2;\ - \ color:black;\ - \ cursor:pointer;\ - \ padding-right:1.2em;\ - \ background-position:top 55% right 0.35em;\ - \ background-size:0.5em;\ - \ background-repeat:no-repeat;\ - \ -webkit-appearance:none;\ - \ -moz-appearance:none;\ - \ appearance:none;\ - \ background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3Cpath fill='rgb(170, 170, 170)' fill-rule='nonzero' d='M1.5 2L3 0H0z'/%3E%3C/svg%3E\");\ - \}\ - \select:focus{\ - \ border:1px solid black;\ - \}\ - \select:not([disabled]):hover{\ - \ border:1px solid black;\ - \}\ - \select:active{\ - \ background-color:#aaaaaa;\ - \}\ - \select[disabled]{\ - \ color:#aaaaaa;\ - \ cursor:not-allowed;\ - \}\ - \select:not([disabled]):focus, select:not([disabled]):hover{\ - \ background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3Cpath fill='rgb(0, 0, 0)' fill-rule='nonzero' d='M1.5 2L3 0H0z'/%3E%3C/svg%3E\");\ - \}\ - \\ - \input[type=text], input[type=password], input[type^=date], input[type=email], input[type=number], input[type=search], input[type=tel], input[type=time], input[type=month], input[type=week], input[type=url]{\ - \ outline:none;\ - \ box-sizing:border-box;\ - \ height:2em;\ - \ margin:0;\ - \ padding:calc(.25em - 1px) 0.5em;\ - \ font-family:inherit;\ - \ font-size:1em;\ - \ border:1px solid #aaaaaa;\ - \ border-radius:2px;\ - \ background:white;\ - \ color:black;\ - \ display:block;\ - \ width:100%;\ - \ line-height:calc(2em - 1px * 2 - (.25em - 1px) * 2);\ - \ -webkit-appearance:none;\ - \ -moz-appearance:none;\ - \ appearance:none;\ - \}\ - \input[type=text]:focus, input[type=password]:focus, input[type^=date]:focus, input[type=email]:focus, input[type=number]:focus, input[type=search]:focus, input[type=tel]:focus, input[type=time]:focus, input[type=month]:focus, input[type=week]:focus, input[type=url]:focus{\ - \ border:1px solid black;\ - \}\ - \input[type=text]::-moz-placeholder, input[type=password]::-moz-placeholder, input[type^=date]::-moz-placeholder, input[type=email]::-moz-placeholder, input[type=number]::-moz-placeholder, input[type=search]::-moz-placeholder, input[type=tel]::-moz-placeholder, input[type=time]::-moz-placeholder, input[type=month]::-moz-placeholder, input[type=week]::-moz-placeholder, input[type=url]::-moz-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \input[type=text]::-webkit-input-placeholder, input[type=password]::-webkit-input-placeholder, input[type^=date]::-webkit-input-placeholder, input[type=email]::-webkit-input-placeholder, input[type=number]::-webkit-input-placeholder, input[type=search]::-webkit-input-placeholder, input[type=tel]::-webkit-input-placeholder, input[type=time]::-webkit-input-placeholder, input[type=month]::-webkit-input-placeholder, input[type=week]::-webkit-input-placeholder, input[type=url]::-webkit-input-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \input[type=text]:-ms-input-placeholder, input[type=password]:-ms-input-placeholder, input[type^=date]:-ms-input-placeholder, input[type=email]:-ms-input-placeholder, input[type=number]:-ms-input-placeholder, input[type=search]:-ms-input-placeholder, input[type=tel]:-ms-input-placeholder, input[type=time]:-ms-input-placeholder, input[type=month]:-ms-input-placeholder, input[type=week]:-ms-input-placeholder, input[type=url]:-ms-input-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \input[type=submit], input[type=button], input[type=reset]{\ - \ outline:none;\ - \ box-sizing:border-box;\ - \ height:2em;\ - \ margin:0;\ - \ padding:calc(.25em - 1px) 0.5em;\ - \ font-family:inherit;\ - \ font-size:1em;\ - \ border:1px solid #aaaaaa;\ - \ border-radius:2px;\ - \ background:white;\ - \ color:black;\ - \ display:inline-block;\ - \ width:auto;\ - \ background:#f2f2f2;\ - \ color:black;\ - \ cursor:pointer;\ - \ -webkit-appearance:none;\ - \ -moz-appearance:none;\ - \ appearance:none;\ - \}\ - \input[type=submit]:focus, input[type=button]:focus, input[type=reset]:focus{\ - \ border:1px solid black;\ - \}\ - \input[type=submit]:not([disabled]):hover, input[type=button]:not([disabled]):hover, input[type=reset]:not([disabled]):hover{\ - \ border:1px solid black;\ - \}\ - \input[type=submit]:active, input[type=button]:active, input[type=reset]:active{\ - \ background-color:#aaaaaa;\ - \}\ - \input[type=submit][disabled], input[type=button][disabled], input[type=reset][disabled]{\ - \ color:#aaaaaa;\ - \ cursor:not-allowed;\ - \}\ - \input[type=color]{\ - \ outline:none;\ - \ box-sizing:border-box;\ - \ height:2em;\ - \ margin:0;\ - \ padding:calc(.25em - 1px) 0.5em;\ - \ font-family:inherit;\ - \ font-size:1em;\ - \ border:1px solid #aaaaaa;\ - \ border-radius:2px;\ - \ background:white;\ - \ color:black;\ - \ display:block;\ - \ width:100%;\ - \ line-height:calc(2em - 1px * 2 - (.25em - 1px) * 2);\ - \ -webkit-appearance:none;\ - \ -moz-appearance:none;\ - \ appearance:none;\ - \ width:6em;\ - \}\ - \input[type=color]:focus{\ - \ border:1px solid black;\ - \}\ - \input[type=color]::-moz-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \input[type=color]::-webkit-input-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \input[type=color]:-ms-input-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \input[type=color]:hover{\ - \ border:1px solid black;\ - \}\ - \input[type=file]{\ - \ outline:none;\ - \ box-sizing:border-box;\ - \ height:2em;\ - \ margin:0;\ - \ padding:calc(.25em - 1px) 0.5em;\ - \ font-family:inherit;\ - \ font-size:1em;\ - \ border:1px solid #aaaaaa;\ - \ border-radius:2px;\ - \ background:white;\ - \ color:black;\ - \ display:inline-block;\ - \ width:auto;\ - \ background:#f2f2f2;\ - \ color:black;\ - \ cursor:pointer;\ - \ display:block;\ - \ width:100%;\ - \ height:auto;\ - \ padding:0.75em 0.5em;\ - \ font-size:12px;\ - \ line-height:1;\ - \}\ - \input[type=file]:focus{\ - \ border:1px solid black;\ - \}\ - \input[type=file]:not([disabled]):hover{\ - \ border:1px solid black;\ - \}\ - \input[type=file]:active{\ - \ background-color:#aaaaaa;\ - \}\ - \input[type=file][disabled]{\ - \ color:#aaaaaa;\ - \ cursor:not-allowed;\ - \}\ - \input[type=checkbox], input[type=radio]{\ - \ margin:-0.2em 0.75em 0 0;\ - \ vertical-align:middle;\ - \}\ - \\ - \textarea{\ - \ outline:none;\ - \ box-sizing:border-box;\ - \ height:2em;\ - \ margin:0;\ - \ padding:calc(.25em - 1px) 0.5em;\ - \ font-family:inherit;\ - \ font-size:1em;\ - \ border:1px solid #aaaaaa;\ - \ border-radius:2px;\ - \ background:white;\ - \ color:black;\ - \ display:block;\ - \ width:100%;\ - \ line-height:calc(2em - 1px * 2 - (.25em - 1px) * 2);\ - \ -webkit-appearance:none;\ - \ -moz-appearance:none;\ - \ appearance:none;\ - \ height:4.5em;\ - \ resize:vertical;\ - \ padding-top:0.5em;\ - \ padding-bottom:0.5em;\ - \}\ - \textarea:focus{\ - \ border:1px solid black;\ - \}\ - \textarea::-moz-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \textarea::-webkit-input-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \textarea:-ms-input-placeholder{\ - \ color:#aaaaaa;\ - \}\ - \\ - \output{\ - \ display:block;\ - \}\ - \\ - \code, kbd, var, samp{\ - \ font-family:Consolas, \"Lucida Console\", Monaco, monospace;\ - \ font-style:normal;\ - \}\ - \\ - \pre{\ - \ overflow-x:auto;\ - \ font-size:0.8em;\ - \ background:linear-gradient(rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%) 0 0, linear-gradient(rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%) 100% 0;\ - \ background-attachment:scroll, scroll;\ - \ background-size:1px 100%, 1px 100%;\ - \ background-repeat:no-repeat, no-repeat;\ - \}\ - \pre > code{\ - \ display:inline-block;\ - \ overflow-x:visible;\ - \ box-sizing:border-box;\ - \ min-width:100%;\ - \ border-right:3px solid white;\ - \ border-left:1px solid white;\ - \}\ - \\ - \hr{\ - \ height:1px;\ - \ margin:2em 0;\ - \ border:0;\ - \ background:#f2f2f2;\ - \}\ - \\ - \details{\ - \ margin:1em 0;\ - \}\ - \details[open]{\ - \ padding-bottom:0.5em;\ - \ border-bottom:1px solid #f2f2f2;\ - \}\ - \\ - \summary{\ - \ display:inline-block;\ - \ font-weight:700;\ - \ border-bottom:1px dashed;\ - \ cursor:pointer;\ - \}\ - \summary::-webkit-details-marker{\ - \ display:none;\ - \}\ - \\ - \noscript{\ - \ color:#d00000;\ - \}\ - \\ - \::selection{\ - \ background:rgba(0, 100, 193, 0.25);\ - \}" - -countriesMap :: JSString -countriesMap = "\ - \\ - \\ - \ World Map\ - \ \ - \\ - \ \ - \ Sudan\ - \ \ - \ \ - \ South Sudan\ - \ \ - \ \ - \ Georgia\ - \ \ - \ \ - \ Abkhazia\ - \ \ - \ \ - \ \ - \ \ - \ South Ossetia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Peru\ - \ \ - \ \ - \ Burkina Faso\ - \ \ - \ \ - \ Libya\ - \ \ - \ \ - \ Belarus\ - \ \ - \ \ - \ Pakistan\ - \ \ - \ \ - \ Azad Jammu and Kashmir\ - \ \ - \ \ - \ \ - \ Indonesia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Yemen\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Madagascar\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Bolivia, Plurinational State of\ - \ \ - \ \ - \ \ - \ \ - \ Serbia\ - \ \ - \ \ - \ Kosovo\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Cote d'Ivoire\ - \ \ - \ \ - \ Algeria\ - \ \ - \ \ - \ Switzerland\ - \ \ - \ \ - \ Cameroon\ - \ \ - \ \ - \ North Macedonia, Republic of\ - \ \ - \ \ - \ Botswana\ - \ \ - \ \ - \ Kenya\ - \ \ - \ \ - \ Jordan\ - \ \ - \ \ - \ Mexico\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ United Arab Emirates\ - \ \ - \ \ - \ \ - \ \ - \ Belize\ - \ \ - \ \ - \ \ - \ \ - \ Brazil\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Sierra Leone\ - \ \ - \ \ - \ \ - \ \ - \ Mali\ - \ \ - \ \ - \ Congo, Democratic Republic of the\ - \ \ - \ \ - \ Italy\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Somalia\ - \ \ - \ \ - \ Somaliland\ - \ \ - \ \ - \ \ - \ Afghanistan\ - \ \ - \ \ - \ Bangladesh\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Dominican Republic\ - \ \ - \ \ - \ \ - \ \ - \ Guinea-Bissau\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Ghana\ - \ \ - \ \ - \ Austria\ - \ \ - \ \ - \ Sweden\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Turkey\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Uganda\ - \ \ - \ \ - \ Mozambique\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Japan\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ New Zealand\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Cuba\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Venezuela, Bolivarian Republic of\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Portugal\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Colombia\ - \ \ - \ \ - \ Mauritania\ - \ \ - \ \ - \ \ - \ \ - \ Angola\ - \ \ - \ \ - \ \ - \ \ - \ Germany\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Thailand\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Papua New Guinea\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Iraq\ - \ \ - \ \ - \ Croatia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Greenland\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Niger\ - \ \ - \ \ - \ Denmark\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Latvia\ - \ \ - \ \ - \ Romania\ - \ \ - \ \ - \ Zambia\ - \ \ - \ \ - \ Myanmar\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Ethiopia\ - \ \ - \ \ - \ Guatemala\ - \ \ - \ \ - \ Suriname\ - \ \ - \ \ - \ Czech Republic\ - \ \ - \ \ - \ Chad\ - \ \ - \ \ - \ Albania\ - \ \ - \ \ - \ Finland\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Syrian Arab Republic\ - \ \ - \ \ - \ Kyrgyzstan\ - \ \ - \ \ - \ Solomon Islands\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Oman\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Panama\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Argentina\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ United Kingdom\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Costa Rica\ - \ \ - \ \ - \ \ - \ \ - \ Paraguay\ - \ \ - \ \ - \ Guinea\ - \ \ - \ \ - \ \ - \ \ - \ Ireland\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Nigeria\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Tunisia\ - \ \ - \ \ - \ \ - \ \ - \ Poland\ - \ \ - \ \ - \ Namibia\ - \ \ - \ \ - \ South Africa\ - \ \ - \ \ - \ Egypt\ - \ \ - \ \ - \ Tanzania, United Republic of\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Saudi Arabia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Vietnam, Socialist Republic of\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Russian Federation\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Southern Kuril Islands\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Crimea\ - \ \ - \ \ - \ \ - \ Haiti\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Bosnia and Herzegovina\ - \ \ - \ \ - \ India\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Canada\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ El Salvador\ - \ \ - \ \ - \ Guyana\ - \ \ - \ \ - \ Belgium\ - \ \ - \ \ - \ Equatorial Guinea\ - \ \ - \ \ - \ \ - \ \ - \ Lesotho\ - \ \ - \ \ - \ Bulgaria\ - \ \ - \ \ - \ Burundi\ - \ \ - \ \ - \ Djibouti\ - \ \ - \ \ - \ Azerbaijan\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Artsakh, Republic of\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Iran, Islamic Republic of\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Malaysia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Philippines\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Uruguay\ - \ \ - \ \ - \ Congo, Republic of the\ - \ \ - \ \ - \ Estonia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Rwanda\ - \ \ - \ \ - \ Armenia\ - \ \ - \ \ - \ Senegal\ - \ \ - \ \ - \ Togo\ - \ \ - \ \ - \ Spain\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Gabon\ - \ \ - \ \ - \ \ - \ \ - \ Hungary\ - \ \ - \ \ - \ Malawi\ - \ \ - \ \ - \ Tajikistan\ - \ \ - \ \ - \ Cambodia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Korea, Republic of\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Honduras\ - \ \ - \ \ - \ \ - \ \ - \ Iceland\ - \ \ - \ \ - \ Nicaragua\ - \ \ - \ \ - \ Chile\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Morocco\ - \ \ - \ \ - \ \ - \ Western Sahara\ - \ \ - \ \ - \ Sahrawi Arab Democratic Republic\ - \ \ - \ \ - \ \ - \ \ - \ Liberia\ - \ \ - \ \ - \ Central African Republic\ - \ \ - \ \ - \ Slovakia\ - \ \ - \ \ - \ Lithuania\ - \ \ - \ \ - \ Zimbabwe\ - \ \ - \ \ - \ Sri Lanka\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Israel\ - \ \ - \ \ - \ \ - \ State of Palestine\ - \ \ - \ \ - \ Gaza Strip\ - \ \ - \ \ - \ West Bank\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Lao People's Democratic Republic\ - \ \ - \ \ - \ Korea, Democratic People's Republic of\ - \ \ - \ \ - \ Greece\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Turkmenistan\ - \ \ - \ \ - \ Ecuador\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Benin\ - \ \ - \ \ - \ Slovenia\ - \ \ - \ \ - \ Norway\ - \ \ - \ Svalbard\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Moldova, Republic of\ - \ \ - \ \ - \ Transnistria\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Ukraine\ - \ \ - \ \ - \ Donetsk People's Republic\ - \ \ - \ \ - \ \ - \ \ - \ Luhansk People's Republic\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Nepal\ - \ \ - \ \ - \ Eritrea\ - \ \ - \ \ - \ \ - \ \ - \ United States of America\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Kazakhstan\ - \ \ - \ \ - \ \ - \ \ - \ French Southern Territories\ - \ \ - \ \ - \ \ - \ \ - \ Uzbekistan\ - \ \ - \ \ - \ Mongolia\ - \ \ - \ \ - \ Bhutan\ - \ \ - \ \ - \ Antarctica\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \\ - \\ - \\ - \\ - \ \ - \ Australia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Christmas Island\ - \ \ - \ \ - \ \ - \ \ - \ Cocos (Keeling) Islands\ - \ \ - \ \ - \ \ - \ \ - \ Heard Island and McDonald Islands\ - \ \ - \ \ - \ \ - \ \ - \ Norfolk Island\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ China\ - \ \ - \ China, People's Republic of\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Hong Kong\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Macao\ - \ \ - \ \ - \ \ - \ \ - \ Taiwan\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ France\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ French Guiana\ - \ \ - \ \ - \ Guadeloupe\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Martinique\ - \ \ - \ \ - \ \ - \ \ - \ Reunion\ - \ \ - \ \ - \ \ - \ \ - \ Mayotte\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Netherlands\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Bonaire, Sint Eustatius and Saba\ - \ \ - \ \ - \ \ - \ \ - \\ - \\ - \\ - \\ - \ \ - \ Lebanon\ - \ \ - \ \ - \ \ - \ \ - \ Montenegro\ - \ \ - \ \ - \ \ - \ \ - \ Eswatini\ - \ \ - \ \ - \ \ - \ \ - \ New Caledonia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Fiji\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Kuwait\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Timor-Leste\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Bahamas\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Vanuatu\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Falkland Islands (Islas Malvinas)\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ South Georgia and the South Sandwich Islands\ - \ \ - \ \ - \ \ - \ \ - \ Gambia\ - \ \ - \ \ - \ \ - \ \ - \ Qatar\ - \ \ - \ \ - \ \ - \ \ - \ Jamaica\ - \ \ - \ \ - \ \ - \ \ - \ Cyprus\ - \ \ - \ \ - \ Northern Cyprus\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Puerto Rico\ - \ \ - \ \ - \ \ - \ \ - \ Brunei\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Trinidad and Tobago\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Cape Verde\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ French Polynesia\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Samoa\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Luxembourg\ - \ \ - \ \ - \ \ - \ \ - \ Comoros\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Mauritius\ - \ \ - \ \ - \ \ - \ \ - \ Faroe Islands\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Sao Tome and Principe\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Virgin Islands, U.S.\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Curaçao\ - \ \ - \ \ - \ \ - \ \ - \ Sint Maarten (Dutch Part)\ - \ \ - \ \ - \ \ - \ \ - \ Dominica\ - \ \ - \ \ - \ \ - \ \ - \ Tonga\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Kiribati\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Micronesia, Federated States of\ - \ \ - \ \ - \ \ - \ \ - \ Bahrain\ - \ \ - \ \ - \ \ - \ \ - \ Andorra\ - \ \ - \ \ - \ \ - \ \ - \ Northern Mariana Islands\ - \ \ - \ \ - \ \ - \ \ - \ Palau\ - \ \ - \ \ - \ \ - \ \ - \ Seychelles, Republic of\ - \ \ - \ \ - \ \ - \ \ - \ British Indian Ocean Territory\ - \ \ - \ \ - \ \ - \ \ - \ Antigua and Barbuda\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Barbados\ - \ \ - \ \ - \ \ - \ \ - \ Turks and Caicos Islands\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Saint Vincent and the Grenadines\ - \ \ - \ \ - \ \ - \ \ - \ Saint Lucia\ - \ \ - \ \ - \ \ - \ \ - \ Grenada\ - \ \ - \ \ - \ \ - \ \ - \ Malta\ - \ \ - \ \ - \ \ - \ \ - \ Maldives, Republic of\ - \ \ - \ \ - \ \ - \ \ - \ Cayman Islands\ - \ \ - \ \ - \ \ - \ \ - \ Saint Kitts and Nevis\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Montserrat\ - \ \ - \ \ - \ \ - \ \ - \ Saint Barthélemy\ - \ \ - \ \ - \ \ - \ \ - \ Niue\ - \ \ - \ \ - \ \ - \ \ - \ Saint Pierre and Miquelon\ - \ \ - \ \ - \ \ - \ \ - \ Cook Islands\ - \ \ - \ \ - \ \ - \ \ - \ Wallis and Futuna\ - \ \ - \ \ - \ \ - \ \ - \ American Samoa\ - \ \ - \ \ - \ \ - \ \ - \ Marshall Islands, Republic of the\ - \ \ - \ \ - \ \ - \ \ - \ Aruba\ - \ \ - \ \ - \ \ - \ \ - \ Liechtenstein\ - \ \ - \ \ - \ \ - \ \ - \ Virgin Islands, British\ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ \ - \ Saint Helena, Ascension and Tristan da Cunha\ - \ \ - \ \ - \ \ - \ \ - \ Jersey\ - \ \ - \ \ - \ \ - \ \ - \ Anguilla\ - \ \ - \ \ - \ \ - \ \ - \ Saint Martin (French Part)\ - \ \ - \ \ - \ \ - \ \ - \ Guernsey\ - \ \ - \ \ - \ \ - \ \ - \ San Marino\ - \ \ - \ \ - \ \ - \ \ - \ Bermuda\ - \ \ - \ \ - \ \ - \ \ - \ Tuvalu\ - \ \ - \ \ - \ \ - \ \ - \ Nauru\ - \ \ - \ \ - \ \ - \ \ - \ Gibraltar\ - \ \ - \ \ - \ \ - \ \ - \ Pitcairn Islands\ - \ \ - \ \ - \ \ - \ \ - \ Monaco\ - \ \ - \ \ - \ \ - \ \ - \ Vatican City\ - \ \ - \ \ - \ \ - \ \ - \ Isle of Man\ - \ \ - \ \ - \ \ - \ \ - \ Guam\ - \ \ - \ \ - \ \ - \ \ - \ Singapore\ - \ \ - \ \ - \ \ - \ \ - \ Tokelau\ - \ \ - \ \ - \ \ - \\ - \\ - \\ - \\ - \" - -prismJs :: JSString -prismJs = "\ - \var _self=\"undefined\"!=typeof window?window:\"undefined\"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\\blang(?:uage)?-([\\w-]+)\\b/i,n=0,e={},M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,\"&\").replace(/=l.reach);y+=m.value.length,m=m.next){var b=m.value;if(t.length>n.length)return;if(!(b instanceof W)){var k,x=1;if(h){if(!(k=z(v,y,n,f))||k.index>=n.length)break;var w=k.index,A=k.index+k[0].length,P=y;for(P+=m.value.length;P<=w;)m=m.next,P+=m.value.length;if(P-=m.value.length,y=P,m.value instanceof W)continue;for(var E=m;E!==t.tail&&(Pl.reach&&(l.reach=N);var j=m.prev;O&&(j=I(t,j,O),y+=O.length),q(t,j,x);var C=new W(o,g?M.tokenize(S,g):S,d,S);if(m=I(t,j,C),L&&I(t,m,L),1l.reach&&(l.reach=_.reach)}}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.next;for(;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=M.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=M.hooks.all[e];if(t&&t.length)for(var r,a=0;r=t[a++];)r(n)}},Token:W};function W(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||\"\").length}function z(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function i(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function I(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function q(e,n,t){for(var r=n.next,a=0;a\"+a.content+\"\"},!u.document)return u.addEventListener&&(M.disableWorkerMessageHandler||u.addEventListener(\"message\",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(M.highlight(r,M.languages[t],t)),a&&u.close()},!1)),M;var t=M.util.currentScript();function r(){M.manual||M.highlightAll()}if(t&&(M.filename=t.src,t.hasAttribute(\"data-manual\")&&(M.manual=!0)),!M.manual){var a=document.readyState;\"loading\"===a||\"interactive\"===a&&t&&t.defer?document.addEventListener(\"DOMContentLoaded\",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)}return M}(_self);\"undefined\"!=typeof module&&module.exports&&(module.exports=Prism),\"undefined\"!=typeof global&&(global.Prism=Prism);\ - \Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\\?[\\s\\S]+?\\?>/,greedy:!0},doctype:{pattern:/\"'[\\]]|\"[^\"]*\"|'[^']*')+(?:\\[(?:[^<\"'\\]]|\"[^\"]*\"|'[^']*'|<(?!!--)|)*\\]\\s*)?>/i,greedy:!0,inside:{\"internal-subset\":{pattern:/(^[^\\[]*\\[)[\\s\\S]+(?=\\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/\"[^\"]*\"|'[^']*'/,greedy:!0},punctuation:/^$|[[\\]]/,\"doctype-tag\":/^DOCTYPE/i,name:/[^\\s<>'\"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\\/?(?!\\d)[^\\s>\\/=$<%]+(?:\\s(?:\\s*[^\\s>\\/=]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))|(?=[\\s/>])))+)?\\s*\\/?>/,greedy:!0,inside:{tag:{pattern:/^<\\/?[^\\s>\\/]+/,inside:{punctuation:/^<\\/?/,namespace:/^[^\\s>\\/:]+:/}},\"special-attr\":[],\"attr-value\":{pattern:/=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:\"attr-equals\"},/\"|'/]}},punctuation:/\\/?>/,\"attr-name\":{pattern:/[^\\s>\\/]+/,inside:{namespace:/^[^\\s>\\/:]+:/}}}},entity:[{pattern:/&[\\da-z]{1,8};/i,alias:\"named-entity\"},/&#x?[\\da-f]{1,8};/i]},Prism.languages.markup.tag.inside[\"attr-value\"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside[\"internal-subset\"].inside=Prism.languages.markup,Prism.hooks.add(\"wrap\",function(a){\"entity\"===a.type&&(a.attributes.title=a.content.replace(/&/,\"&\"))}),Object.defineProperty(Prism.languages.markup.tag,\"addInlined\",{value:function(a,e){var s={};s[\"language-\"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={\"included-cdata\":{pattern://i,inside:s}};t[\"language-\"+e]={pattern:/[\\s\\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp(\"(<__[^>]*>)(?:))*\\\\]\\\\]>|(?!)\".replace(/__/g,function(){return a}),\"i\"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore(\"markup\",\"cdata\",n)}}),Object.defineProperty(Prism.languages.markup.tag,\"addAttribute\",{value:function(a,e){Prism.languages.markup.tag.inside[\"special-attr\"].push({pattern:RegExp(\"(^|[\\\"'\\\\s])(?:\"+a+\")\\\\s*=\\\\s*(?:\\\"[^\\\"]*\\\"|'[^']*'|[^\\\\s'\\\">=]+(?=[\\\\s>]))\",\"i\"),lookbehind:!0,inside:{\"attr-name\":/^[^\\s=]+/,\"attr-value\":{pattern:/=[\\s\\S]+/,inside:{value:{pattern:/(^=\\s*([\"']|(?![\"'])))\\S[\\s\\S]*(?=\\2$)/,lookbehind:!0,alias:[e,\"language-\"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:\"attr-equals\"},/\"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend(\"markup\",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;\ - \!function(s){var e=/(?:\"(?:\\\\(?:\\r\\n|[\\s\\S])|[^\"\\\\\\r\\n])*\"|'(?:\\\\(?:\\r\\n|[\\s\\S])|[^'\\\\\\r\\n])*')/;s.languages.css={comment:/\\/\\*[\\s\\S]*?\\*\\//,atrule:{pattern:/@[\\w-](?:[^;{\\s]|\\s+(?![\\s{]))*(?:;|(?=\\s*\\{))/,inside:{rule:/^@[\\w-]+/,\"selector-function-argument\":{pattern:/(\\bselector\\s*\\(\\s*(?![\\s)]))(?:[^()\\s]|\\s+(?![\\s)])|\\((?:[^()]|\\([^()]*\\))*\\))+(?=\\s*\\))/,lookbehind:!0,alias:\"selector\"},keyword:{pattern:/(^|[^\\w-])(?:and|not|only|or)(?![\\w-])/,lookbehind:!0}}},url:{pattern:RegExp(\"\\\\burl\\\\((?:\"+e.source+\"|(?:[^\\\\\\\\\\r\\n()\\\"']|\\\\\\\\[^])*)\\\\)\",\"i\"),greedy:!0,inside:{function:/^url/i,punctuation:/^\\(|\\)$/,string:{pattern:RegExp(\"^\"+e.source+\"$\"),alias:\"url\"}}},selector:{pattern:RegExp(\"(^|[{}\\\\s])[^{}\\\\s](?:[^{};\\\"'\\\\s]|\\\\s+(?![\\\\s{])|\"+e.source+\")*(?=\\\\s*\\\\{)\"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\\w\\xA0-\\uFFFF])(?!\\s)[-_a-z\\xA0-\\uFFFF](?:(?!\\s)[-\\w\\xA0-\\uFFFF])*(?=\\s*:)/i,lookbehind:!0},important:/!important\\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined(\"style\",\"css\"),t.tag.addAttribute(\"style\",\"css\"))}(Prism);\ - \Prism.languages.haskell={comment:{pattern:/(^|[^-!#$%*+=?&@|~.:<>^\\\\\\/])(?:--(?:(?=.)[^-!#$%*+=?&@|~.:<>^\\\\\\/].*|$)|\\{-[\\s\\S]*?-\\})/m,lookbehind:!0},char:{pattern:/'(?:[^\\\\']|\\\\(?:[abfnrtv\\\\\"'&]|\\^[A-Z@[\\]^_]|ACK|BEL|BS|CAN|CR|DC1|DC2|DC3|DC4|DEL|DLE|EM|ENQ|EOT|ESC|ETB|ETX|FF|FS|GS|HT|LF|NAK|NUL|RS|SI|SO|SOH|SP|STX|SUB|SYN|US|VT|\\d+|o[0-7]+|x[0-9a-fA-F]+))'/,alias:\"string\"},string:{pattern:/\"(?:[^\\\\\"]|\\\\(?:\\S|\\s+\\\\))*\"/,greedy:!0},keyword:/\\b(?:case|class|data|deriving|do|else|if|in|infixl|infixr|instance|let|module|newtype|of|primitive|then|type|where)\\b/,\"import-statement\":{pattern:/(^[\\t ]*)import\\s+(?:qualified\\s+)?(?:[A-Z][\\w']*)(?:\\.[A-Z][\\w']*)*(?:\\s+as\\s+(?:[A-Z][\\w']*)(?:\\.[A-Z][\\w']*)*)?(?:\\s+hiding\\b)?/m,lookbehind:!0,inside:{keyword:/\\b(?:as|hiding|import|qualified)\\b/,punctuation:/\\./}},builtin:/\\b(?:abs|acos|acosh|all|and|any|appendFile|approxRational|asTypeOf|asin|asinh|atan|atan2|atanh|basicIORun|break|catch|ceiling|chr|compare|concat|concatMap|const|cos|cosh|curry|cycle|decodeFloat|denominator|digitToInt|div|divMod|drop|dropWhile|either|elem|encodeFloat|enumFrom|enumFromThen|enumFromThenTo|enumFromTo|error|even|exp|exponent|fail|filter|flip|floatDigits|floatRadix|floatRange|floor|fmap|foldl|foldl1|foldr|foldr1|fromDouble|fromEnum|fromInt|fromInteger|fromIntegral|fromRational|fst|gcd|getChar|getContents|getLine|group|head|id|inRange|index|init|intToDigit|interact|ioError|isAlpha|isAlphaNum|isAscii|isControl|isDenormalized|isDigit|isHexDigit|isIEEE|isInfinite|isLower|isNaN|isNegativeZero|isOctDigit|isPrint|isSpace|isUpper|iterate|last|lcm|length|lex|lexDigits|lexLitChar|lines|log|logBase|lookup|map|mapM|mapM_|max|maxBound|maximum|maybe|min|minBound|minimum|mod|negate|not|notElem|null|numerator|odd|or|ord|otherwise|pack|pi|pred|primExitWith|print|product|properFraction|putChar|putStr|putStrLn|quot|quotRem|range|rangeSize|read|readDec|readFile|readFloat|readHex|readIO|readInt|readList|readLitChar|readLn|readOct|readParen|readSigned|reads|readsPrec|realToFrac|recip|rem|repeat|replicate|return|reverse|round|scaleFloat|scanl|scanl1|scanr|scanr1|seq|sequence|sequence_|show|showChar|showInt|showList|showLitChar|showParen|showSigned|showString|shows|showsPrec|significand|signum|sin|sinh|snd|sort|span|splitAt|sqrt|subtract|succ|sum|tail|take|takeWhile|tan|tanh|threadToIOResult|toEnum|toInt|toInteger|toLower|toRational|toUpper|truncate|uncurry|undefined|unlines|until|unwords|unzip|unzip3|userError|words|writeFile|zip|zip3|zipWith|zipWith3)\\b/,number:/\\b(?:\\d+(?:\\.\\d+)?(?:e[+-]?\\d+)?|0o[0-7]+|0x[0-9a-f]+)\\b/i,operator:[{pattern:/`(?:[A-Z][\\w']*\\.)*[_a-z][\\w']*`/,greedy:!0},{pattern:/(\\s)\\.(?=\\s)/,lookbehind:!0},/[-!#$%*+=?&@|~:<>^\\\\\\/][-!#$%*+=?&@|~.:<>^\\\\\\/]*|\\.[-!#$%*+=?&@|~.:<>^\\\\\\/]+/],hvariable:{pattern:/\\b(?:[A-Z][\\w']*\\.)*[_a-z][\\w']*/,inside:{punctuation:/\\./}},constant:{pattern:/\\b(?:[A-Z][\\w']*\\.)*[A-Z][\\w']*/,inside:{punctuation:/\\./}},punctuation:/[{}[\\];(),.:]/},Prism.languages.hs=Prism.languages.haskell;\ - \" - -prismCss :: JSString -prismCss = "\ - \/* PrismJS 1.25.0\ - \https://prismjs.com/download.html#themes=prism&languages=haskell */\ - \code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}\ - \" diff --git a/examples/simple-routing/Pages.hs b/examples/simple-routing/Pages.hs deleted file mode 100644 index b4f9af3..0000000 --- a/examples/simple-routing/Pages.hs +++ /dev/null @@ -1,205 +0,0 @@ -module Pages where - -import Control.Monad.IO.Class -import Control.Monad.Reader -import Data.Foldable -import Data.List qualified as List -import Data.Maybe -import Data.Ord -import HtmlT -import JavaScript.Compat.Marshal -import JavaScript.Compat.String (JSString) -import JavaScript.Compat.String qualified as JSS - -import "this" Assets -import "this" Router -import "this" Utils - -homePage :: Html () -homePage = unsafeHtml $ "\ - \

How routing works

\ - \

Inside the \ - \Router \ - \module there is a definition of type Route:

\ - \
" <> highlightHaskell "\
-  \data Route\n\
-  \  = HomeR -- matches root route\n\
-  \  | CountriesMapR CountriesMapQ -- example: #map?selected=ru\n\
-  \  | CountriesListR CountriesListQ -- example: #list?page=3"
-  <> "
\ - \

Here Route defines the list of webpages in the site. \ - \Constructor parameters (like CountriesMapQ) indicate \ - \that this page takes some information from the URL string encoded in GET \ - \parameters or URL segments. By convention route contructors have suffix \ - \-R and constructor parameters has suffix -Q

\ - \

Another importants definitions are these two functions:\ - \

" <> highlightHaskell "\
-  \parseRoute :: UrlParts -> Maybe Route\n\
-  \parseRoute = \\case\n\
-  \  Url [] [] -> Just HomeR\n\
-  \  Url [\"map\"] q\n\
-  \    | selected <- List.lookup \"selected\" q\n\
-  \    -> Just $ CountriesMapR CountriesMapQ{selected}\n\
-  \  Url [\"list\"] q\n\
-  \    | search <- List.lookup \"search\" q\n\
-  \    , page <- parsePage $ List.lookup \"page\" q\n\
-  \    , sort_dir <- parseSortDir $ List.lookup \"sort_dir\" q\n\
-  \    , sort_by <- parseSortBy $ List.lookup \"sort_by\" q\n\
-  \    -> Just $ CountriesListR CountriesListQ{search, page, sort_dir, sort_by}"
-  <> "
" <> highlightHaskell "\
-  \printRoute :: Route -> UrlParts\n\
-  \printRoute = \\case\n\
-  \  HomeR -> Url [] []\n\
-  \  CountriesMapR q -> Url [\"map\"] $ catMaybes\n\
-  \    [ (\"selected\",) <$> q.selected ]\n\
-  \  CountriesListR q -> Url [\"list\"] $ catMaybes\n\
-  \    [ (\"search\",) <$> mfilter (/=\"\") q.search\n\
-  \    , (\"page\",) <$> printPage q.page\n\
-  \    , (\"sort_dir\",) <$> printSortDir q.sort_dir\n\
-  \    , (\"sort_by\",) <$> printSortBy q.sort_by\n\
-  \    ]"
-  <>  "
\ - \With help of haskell guarded pattern-match syntax it's easy to convert a \ - \URL in form of UrlParts to a structured datatype like \ - \Route and other way around. The type Route and \ - \these two functions conclude the portable part of the routing mechanism. \ - \They can and should be shared with backend code to construct correct URLs \ - \and implement backend part of HTML5-style routing.

\ - \

Last thing we need to run the site is this auxiliary function \ - \mkUrlHashRef \ - \that creates a DynRef JSString — dynamic value containing current \ - \hash-string from the browser. When parsed to Dynamic Route \ - \and then mapped with (<&>) operator to \ - \Dynamic (Html ()) the dyn function can be used to \ - \attach the contents of dynamic pages to the application.\ - \

" <> highlightHaskell "\
-  \dyn $ routeDyn <&> \\case\n\
-  \  HomeR -> homePage\n\
-  \  CountriesMapR q -> countriesMapPage q\n\
-  \  CountriesListR q -> countriesListPage q"
-  <> "

" - -countriesListPage :: CountriesListQ -> Html () -countriesListPage q = div_ [class_ "CountriesList"] do - searchQueryRef <- newRef q - form_ do - onOptions "submit" (ListenerOpts True True True) \_event -> do - newRoute <- toUrl . CountriesListR . (\s -> s{page = 1}) <$> readRef searchQueryRef - pushUrl newRoute - div_ [style_ "display:flex;"] do - input_ - [ type_ "text" , placeholder_ "Search countries by title", autofocus_ True - ] do - dynValue $ fromMaybe "" . (.search) <$> fromRef searchQueryRef - on "input" $ decodeEvent valueDecoder $ - modifyRef searchQueryRef . (\v s -> s{search = v}) . Just - button_ [type_ "submit"] "Search" - table_ do - thead_ $ tr_ do - th_ "" - thSort SortByTitle "Country Name" - thSort SortByRegion "Region" - thSort SortBySubregion "Subregion" - thSort SortByPopulation "Population" - tbody_ do - for_ pageResults \(n, country) -> tr_ do - td_ do text (JSS.pack (show @Int n)) - td_ do - a_ [href_ (mkMapLink country.code)] do - for_ country.flag_icon - (img_ . (>> style_ "display:inline; margin-right: 6px"). src_) - text country.title - td_ do text country.region - td_ do text country.subregion - td_ do text (JSS.pack (show country.population)) - center_ do - for_ (paginate total q.page itemsPerPage) \case - Nothing -> - button_ [disabled_ True] "..." - Just p -> a_ - [ href_ (toUrl (CountriesListR q {page = p}))] $ - button_ [disabled_ (q.page == p)] $ text $ JSS.pack $ show p - dl_ do - dt_ "Country" - dd_ $ unsafeHtml "The word country comes from \ - \Old French contrée, which derives from \ - \Vulgar Latin (terra) contrata (\"(land) lying \ - \opposite\"; \"(land) spread before\"), derived from contra \ - \(\"against, opposite\"). It most likely entered the English language \ - \after the Franco-Norman invasion\ - \ during the 11th century." - where - thSort sortBy title = th_ [style_ "cursor: pointer"] do - text title - case (q.sort_by, q.sort_dir) of - (sortVal, Asc) | sortVal == sortBy -> text "▲" - (sortVal, Desc) | sortVal == sortBy -> text "▼" - otherwise -> text "" - on_ "click" do pushUrl $ toUrl . CountriesListR . toggleSortBy sortBy $ q - - toggleSortBy sortBy q - | q.sort_by == sortBy = q {sort_dir = flipDir q.sort_dir} - | otherwise = q {sort_by = sortBy, sort_dir = Asc} - where - flipDir = \case Asc -> Desc; Desc -> Asc - - offset = pred q.page * itemsPerPage - total = Prelude.length countryResults - pageResults = Prelude.zip [offset + 1..] - . Prelude.take itemsPerPage - . Prelude.drop offset - $ countryResults - countryResults = List.sortOn countrySortDir - . Prelude.filter countryFilter - $ countries - countryFilter country = case q.search of - Just needle -> - JSS.isInfixOf (JSS.toLower needle) (JSS.toLower country.title) - Nothing -> True - countrySortBy = case q.sort_by of - SortByTitle -> Left . (.title) - SortByRegion -> Right . Left . (.region) - SortBySubregion -> Right . Right . Left . (.subregion) - SortByPopulation -> Right . Right . Right . (.population) - countrySortDir = case q.sort_dir of - Asc -> Left . countrySortBy - Desc -> Right . Down . countrySortBy - itemsPerPage = 40 - mkMapLink = toUrl . CountriesMapR . CountriesMapQ . Just . JSS.toLower - -countriesMapPage :: CountriesMapQ -> Html () -countriesMapPage q = - div_ [class_ "CountriesMap"] $ - figure_ $ center_ do - unsafeHtml countriesMap - figcaption_ "political map of the planet Earth" - centerEl <- asks html_current_element - liftIO $ js_selectCountry centerEl $ maybeToNullable $ - fmap JSS.toJSValPure q.selected - on "click" \event -> do - mcode <- fmap JSS.fromJSValPure . nullableToMaybe <$> - liftIO (js_svgClickGetCountryCode event) - mapM_ (pushUrl . toUrl . CountriesMapR . CountriesMapQ . Just) mcode - -paginate - :: Int -- ^ Total number of items - -> Int -- ^ Current page - -> Int -- ^ Items per page - -> [Maybe Int] -- ^ List of page links, Nothing stands for ellipsis -paginate totalItems curPage limit - | totalPages <= maxLinks = - fmap Just [1..totalPages] - | curPage <= 7 = - fmap Just [1..8] <> [Nothing, Just totalPages] - | curPage >= totalPages - 6 = - [Just 1, Nothing] <> fmap Just [(totalPages - 8)..totalPages] - | otherwise = - [Just 1, Nothing] <> fmap Just [(curPage - 2)..(curPage + 3)] - <> [Nothing, Just totalPages] - where - (pageQuot, pageRem) = totalItems `divMod` limit - totalPages = if pageRem == 0 then pageQuot else pageQuot + 1 - maxLinks = 10 diff --git a/examples/simple-routing/Router.hs b/examples/simple-routing/Router.hs deleted file mode 100644 index 386e25a..0000000 --- a/examples/simple-routing/Router.hs +++ /dev/null @@ -1,147 +0,0 @@ -module Router where - -import Control.Monad -import Data.Bifunctor -import Data.List qualified as List -import Data.Maybe -import Data.Function -import GHC.Generics -import Text.Read -import JavaScript.Compat.String (JSString(..)) -import JavaScript.Compat.String qualified as JSS - -data UrlParts = Url - { partsPath :: [JSString] -- ^ Path segments - , partsQuery :: [(JSString, JSString)] -- ^ GET parameters - } deriving (Eq, Show, Generic) - -data Route - = HomeR - | CountriesMapR CountriesMapQ - | CountriesListR CountriesListQ - deriving (Eq, Show, Generic) - -data CountriesListQ = CountriesListQ - { search :: Maybe JSString - , page :: Int - , sort_by :: CountrySortBy - , sort_dir :: SortDir - } deriving (Eq, Show, Generic) - -data CountriesMapQ = CountriesMapQ - { selected :: Maybe JSString - } deriving (Eq, Show, Generic) - -data SortDir = Asc | Desc - deriving (Eq, Show, Generic) - -data CountrySortBy - = SortByTitle - | SortByPopulation - | SortByRegion - | SortBySubregion - deriving (Eq, Show, Generic) - -parseRoute :: UrlParts -> Maybe Route -parseRoute = \case - Url [] [] -> Just HomeR - Url ["map"] q - | selected <- List.lookup "selected" q - -> Just $ CountriesMapR CountriesMapQ{selected} - Url ["list"] q - | search <- List.lookup "search" q - , page <- parsePage $ List.lookup "page" q - , sort_dir <- parseSortDir $ List.lookup "sort_dir" q - , sort_by <- parseSortBy $ List.lookup "sort_by" q - -> Just $ CountriesListR CountriesListQ{search, page, sort_dir, sort_by} - _ -> Nothing - where - parsePage = fromMaybe defaultCountriesListQ.page - . (parseIntQuery =<<) - parseSortDir = \case - Just "asc" -> Asc - Just "desc" -> Desc - _ -> defaultCountriesListQ.sort_dir - parseSortBy = \case - Just "title" -> SortByTitle - Just "population" -> SortByPopulation - Just "region" -> SortByRegion - Just "subregion" -> SortBySubregion - _ -> defaultCountriesListQ.sort_by - parseIntQuery = readMaybe . JSS.unpack - -printRoute :: Route -> UrlParts -printRoute = \case - HomeR -> Url [] [] - CountriesMapR q -> Url ["map"] $ catMaybes - [ ("selected",) <$> q.selected ] - CountriesListR q -> Url ["list"] $ catMaybes - [ ("search",) <$> mfilter (/="") q.search - , ("page",) <$> printPage q.page - , ("sort_dir",) <$> printSortDir q.sort_dir - , ("sort_by",) <$> printSortBy q.sort_by - ] - where - printPage = fmap toIntQuery . - mfilter (/=defaultCountriesListQ.page) . Just - printSortDir = fmap (\case - Asc -> "asc" - Desc -> "desc") . - mfilter (/=defaultCountriesListQ.sort_dir) . Just - printSortBy = fmap (\case - SortByTitle -> "title" - SortByPopulation -> "population" - SortByRegion -> "region" - SortBySubregion -> "subregion") . - mfilter (/=defaultCountriesListQ.sort_by) . Just - toIntQuery = JSS.pack . show - -defaultCountriesListQ :: CountriesListQ -defaultCountriesListQ = CountriesListQ - { search = Nothing - , page = 1 - , sort_by = SortByPopulation - , sort_dir = Desc - } - -defaultCountriesMapQ :: CountriesMapQ -defaultCountriesMapQ = CountriesMapQ - { selected = Nothing - } - -toUrl :: Route -> JSString -toUrl = ("#"<>) . partsToText . printRoute - -fromUrl :: JSString -> Maybe Route -fromUrl url = url - & JSS.stripPrefix "#" - & fromMaybe url - & textToParts - & parseRoute - -partsToText :: UrlParts -> JSString -partsToText (Url s q) = JSS.intercalate "?" (segments : query) - where - segments = - JSS.intercalate "/" $ fmap JSS.encodeURIComponent s - query = q - & fmap (bimap JSS.encodeURIComponent JSS.encodeURIComponent) - & fmap (\(k, v) -> k <> "=" <> v) - & List.filter (not . JSS.null) - & JSS.intercalate "&" - & List.filter (not . JSS.null) . (:[]) - -textToParts :: JSString -> UrlParts -textToParts t = Url segments query - where - (segmentsStr, queryStr) = breakOn1 "?" t - segments = segmentsStr - & JSS.splitOn "/" - & List.filter (not . JSS.null) - & fmap JSS.decodeURIComponent - query = queryStr - & JSS.splitOn "&" - & List.filter (not . JSS.null) - & fmap (breakOn1 "=" . JSS.decodeURIComponent) - breakOn1 s t = - let (a, b) = JSS.breakOn s t in (a, JSS.drop 1 b) diff --git a/examples/simple-routing/Utils.hs b/examples/simple-routing/Utils.hs deleted file mode 100644 index bbf873c..0000000 --- a/examples/simple-routing/Utils.hs +++ /dev/null @@ -1,94 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE ForeignFunctionInterface #-} -{-# LANGUAGE JavaScriptFFI #-} -module Utils where - -import Control.Monad.IO.Class -import Data.Coerce -import HtmlT -import JavaScript.Compat.Foreign.Callback -import JavaScript.Compat.Marshal -import JavaScript.Compat.Prim -import JavaScript.Compat.String (JSString) -import JavaScript.Compat.String qualified as JSS -import Unsafe.Coerce - -mkUrlHashRef :: MonadReactive m => m (DynRef JSString) -mkUrlHashRef = do - initial <- liftIO js_readUrlHash - routeRef <- newRef initial - win <- liftIO getCurrentWindow - popStateCb <- liftIO $ asyncCallback $ - js_readUrlHash >>= dynStep . writeRef routeRef - liftIO $ js_callMethod2 (coerce win) "addEventListener" - (JSS.toJSValPure "popstate") (unsafeCoerce popStateCb) - installFinalizer do - js_callMethod2 (coerce win) "removeEventListener" - (JSS.toJSValPure "popstate") (unsafeCoerce popStateCb) - releaseCallback popStateCb - return routeRef - -pushUrl :: MonadIO m => JSString -> m () -pushUrl url = liftIO $ js_pushHref url - -highlightHaskell :: JSString -> JSString -highlightHaskell = js_highlightHaskell - -insertScript :: JSString -> IO () -insertScript = js_insertScript - -#if defined(javascript_HOST_ARCH) -foreign import javascript unsafe - "(function(el, code){\ - if (!code) return;\ - var svgGroup = el.querySelector('#' + code); if (!svgGroup) return;\ - var svgPaths = svgGroup instanceof SVGPathElement ? [svgGroup] : svgGroup.querySelectorAll('path');\ - for (var i = 0; i < svgPaths.length; i++) {\ - svgPaths[i].classList.add('selected');\ - }\ - svgGroup.parentElement.appendChild(svgGroup);\ - })" - js_selectCountry :: DOMElement -> Nullable JSVal -> IO () - -foreign import javascript unsafe - "(function(event){\ - var iter = event.target;\ - for(;;){\ - if (!iter || !iter.parentNode) break;\ - /* immediate children contains the country code */\ - if (iter.parentNode instanceof SVGSVGElement) return iter.id;\ - iter = iter.parentNode;\ - }\ - return null;\ - })" - js_svgClickGetCountryCode :: DOMEvent -> IO (Nullable JSVal) - -foreign import javascript unsafe - "((s) => Prism.highlight(s, Prism.languages.haskell, 'haskell'))" - js_highlightHaskell :: JSString -> JSString - -foreign import javascript unsafe - "(function(script){\ - var scriptEl = document.createElement('script');\ - scriptEl.innerText = script;\ - document.head.appendChild(scriptEl);\ - })" - js_insertScript :: JSString -> IO () -foreign import javascript unsafe - "(function(){\ - return location.hash;\ - })" - js_readUrlHash :: IO JSString -foreign import javascript unsafe - "(function(url){\ - return location = url;\ - })" - js_pushHref :: JSString -> IO () -#else -js_selectCountry :: DOMElement -> Nullable JSVal -> IO () = errorGhcjsOnly -js_svgClickGetCountryCode :: DOMEvent -> IO (Nullable JSVal) = errorGhcjsOnly -js_highlightHaskell :: JSString -> JSString = errorGhcjsOnly -js_insertScript :: JSString -> IO () = errorGhcjsOnly -js_readUrlHash :: IO JSString = errorGhcjsOnly -js_pushHref :: JSString -> IO () = errorGhcjsOnly -#endif diff --git a/examples/simple-routing/prism.externs b/examples/simple-routing/prism.externs deleted file mode 100644 index 34e97e8..0000000 --- a/examples/simple-routing/prism.externs +++ /dev/null @@ -1,16 +0,0 @@ -/** @const */ -var Prism = {}; - -/** @const */ -Prism.languages = {}; - -/** @const */ -Prism.languages.haskell = {}; - -/** - * @param {string} code - * @param {!Object} language - * @param {string} languageString - * @return {string} - */ -Prism.highlight = function(code, language, languageString) {}; \ No newline at end of file diff --git a/examples/simple-routing/simple-routing.hs b/examples/simple-routing/simple-routing.hs deleted file mode 100644 index 26a8d46..0000000 --- a/examples/simple-routing/simple-routing.hs +++ /dev/null @@ -1,68 +0,0 @@ -import Control.Monad -import Control.Monad.IO.Class -import Data.Maybe -import Data.Functor -import HtmlT -import JavaScript.Compat.String (JSString(..)) - -import "this" Pages -import "this" Router -import "this" Utils -import "this" Assets - -main :: IO () -main = void $ attachToBody do - liftIO $ insertScript prismJs - el "style" (text awsmCss) - el "style" (text customCss) - el "style" (text prismCss) - urlHashRef <- mkUrlHashRef - let routeDyn = fromMaybe HomeR . fromUrl <$> fromRef urlHashRef - header_ do - h1_ "Simple in-browser routing example" - p_ do - "See the source on the " - a_ [href_ "https://github.com/lagunoff/htmlt/blob/master/examples/\ - \simple-routing/"] "github" - nav_ $ ul_ do - let link t r = li_ $ a_ [href_ (toUrl r)] $ text t - link "Home" HomeR - link "List of Countries" $ CountriesListR defaultCountriesListQ - link "Countries on the Map" $ CountriesMapR defaultCountriesMapQ - main_ $ dyn $ routeDyn <&> \case - HomeR -> homePage - CountriesMapR q -> countriesMapPage q - CountriesListR q -> countriesListPage q - footer_ $ p_ $ a_ [href_ "https://github.com/lagunoff"] "Vladislav Lagunov" - -customCss :: JSString -customCss = "\ - \body header, body main, body footer, body article {\ - \ max-width: 80rem;\ - \}\ - \pre {\ - \ border-left: solid 8px rgb(0 0 0 / 14%);\ - \ padding-left: 16px;\ - \ background: transparent;\ - \}\ - \.CountriesList table {\ - \ width: 100%;;\ - \}\ - \.CountriesList table th, .CountriesList table td {\ - \ white-space: nowrap;\ - \}\ - \.CountriesList table th:last-child, .CountriesList table td:last-child {\ - \ width: 99%;\ - \ text-align: right;\ - \}\ - \.CountriesMap svg path.selected {\ - \ fill: #bfd3ff !important;\ - \ stroke: #4175e8;\ - \ stroke-width: 1;\ - \}\ - \.CountriesMap svg path {\ - \ cursor: pointer;\ - \}\ - \.CountriesMap svg path:hover {\ - \ fill: #ccc;\ - \}" diff --git a/examples/todomvc/TodoItem.hs b/examples/todomvc/TodoItem.hs deleted file mode 100644 index 8f566f9..0000000 --- a/examples/todomvc/TodoItem.hs +++ /dev/null @@ -1,109 +0,0 @@ -module TodoItem where - -import Control.Monad.State -import Data.Maybe -import GHC.Generics (Generic) -import HtmlT -import JavaScript.Compat.Marshal -import JavaScript.Compat.Prim -import JavaScript.Compat.String (JSString) -import JavaScript.Compat.String qualified as JSS - -import "this" Utils - -data TodoItemConfig = TodoItemConfig - { state_ref :: DynRef TodoItemState - , is_hidden_dyn :: Dynamic Bool - , ask_delete_item :: Step () - } - -data TodoItemState = TodoItemState - { title :: JSString - , completed :: Bool - , editing :: Maybe JSString - } deriving stock (Show, Eq, Generic) - -data TodoItemAction a where - CancelAction :: TodoItemConfig -> TodoItemAction () - CommitAction :: TodoItemConfig -> TodoItemAction () - InputAction :: TodoItemConfig -> JSString -> TodoItemAction () - DoubleClickAction :: TodoItemConfig -> JSVal -> TodoItemAction () - CheckedAction :: TodoItemConfig -> Bool -> TodoItemAction () - KeydownAction :: TodoItemConfig -> Int -> TodoItemAction () - -eval :: TodoItemAction a -> Step a -eval = \case - CancelAction cfg -> - modifyRef cfg.state_ref \s -> s{editing=Nothing} - CommitAction cfg -> do - state <- readRef cfg.state_ref - case state.editing of - Just "" -> - cfg.ask_delete_item - Just t -> - modifyRef cfg.state_ref \s -> s {editing=Nothing, title = t} - Nothing -> - pure () - InputAction cfg newVal -> - modifyRef cfg.state_ref \s -> s{editing = Just newVal} - DoubleClickAction cfg targetEl -> do - modifyRef cfg.state_ref \s -> s {editing = Just s.title} - liftIO $ js_todoItemInputFocus targetEl - CheckedAction cfg isChecked -> do - modifyRef cfg.state_ref \s -> s{completed = isChecked} - KeydownAction cfg key -> case key of - 13 {- Enter -} -> eval (CommitAction cfg) - 27 {- Escape -} -> eval (CancelAction cfg) - _ -> return () - -html :: TodoItemConfig -> Html () -html cfg = li_ do - let - completedDyn = (.completed) <$> fromRef cfg.state_ref - editingDyn = isJust . (.editing) <$> fromRef cfg.state_ref - valueDyn = fromMaybe "" . (.editing) <$> fromRef cfg.state_ref - toggleClass "completed" completedDyn - toggleClass "editing" editingDyn - toggleClass "hidden" cfg.is_hidden_dyn - div_ [class_ "view"] do - on "dblclick" $ decodeEvent (propDecoder "target") $ - eval . DoubleClickAction cfg - input_ [class_ "toggle", type_ "checkbox"] do - dynChecked $ (.completed) <$> fromRef cfg.state_ref - on "change" $ decodeEvent checkedDecoder $ - eval . CheckedAction cfg - label_ $ dynText $ (.title) <$> fromRef cfg.state_ref - button_ [class_ "destroy"] do - on_ "click" cfg.ask_delete_item - input_ [class_ "edit", type_ "text"] do - dynValue valueDyn - on "input" $ decodeEvent valueDecoder $ - eval . InputAction cfg - on "keydown" $ decodeEvent keyCodeDecoder $ - eval . KeydownAction cfg - on_ "blur" $ - eval (CommitAction cfg) - -emptyTodoItemState :: TodoItemState -emptyTodoItemState = TodoItemState "" False Nothing - -instance ToJSVal TodoItemState where - toJSVal s = do - title <- toJSVal s.title - completed <- toJSVal s.completed - editing <- toJSVal s.editing - return $ js_buildObjectI3 - (JSS.toJSValPure "title") title - (JSS.toJSValPure "completed") completed - (JSS.toJSValPure "editing") editing - -instance FromJSVal TodoItemState where - fromJSVal j = do - mtitle <- fromJSVal =<< getProp j "title" - mcompleted <- fromJSVal =<< getProp j "completed" - mediting <- fromJSVal =<< getProp j "editing" - return do - title <- mtitle - completed <- mcompleted - editing <- mediting - return TodoItemState {..} diff --git a/examples/todomvc/TodoList.hs b/examples/todomvc/TodoList.hs deleted file mode 100644 index a928d9b..0000000 --- a/examples/todomvc/TodoList.hs +++ /dev/null @@ -1,585 +0,0 @@ -module TodoList where - -import Control.Monad.IO.Class -import Data.Foldable -import Data.List qualified as List -import Data.Maybe -import GHC.Generics (Generic) -import HtmlT -import JavaScript.Compat.Marshal -import JavaScript.Compat.String (JSString(..)) -import JavaScript.Compat.String qualified as JSS - -import "this" TodoItem qualified as TodoItem -import "this" Utils - -data TodoListConfig = TodoListConfig - { state_ref :: DynRef TodoListState - } - -data TodoListState = TodoListState - { title :: JSString - , items :: [TodoItem.TodoItemState] - , filter :: Filter - } deriving (Show, Eq, Generic) - -data Filter = All | Active | Completed - deriving (Show, Eq, Generic) - -newtype LocalStorageTodoItems = LocalStorageTodoItems - { unLocalStorageTodoItems :: [TodoItem.TodoItemState] - } deriving newtype (ToJSVal, FromJSVal) - -data TodoListAction a where - InitAction :: ReactiveEnv -> DynRef JSString -> TodoListAction (DynRef TodoListState) - ToggleAllAction :: TodoListConfig -> Bool -> TodoListAction () - InputAction :: TodoListConfig -> JSString -> TodoListAction () - CommitAction :: TodoListConfig -> TodoListAction () - KeydownAction :: TodoListConfig -> Int -> TodoListAction () - DeleteItemAction :: TodoListConfig -> Int -> TodoListAction () - ClearCompletedAction :: TodoListConfig -> TodoListAction () - -eval :: TodoListAction a -> Step a -eval = \case - InitAction renv urlHashRef -> do - let parseFilter' = fromMaybe All . parseFilter - todos <- fromMaybe [] . fmap unLocalStorageTodoItems <$> liftIO localStorageGet - initFilter <- parseFilter' <$> readRef urlHashRef - todosRef <- execReactiveT renv do - todosRef <- newRef $ TodoListState "" todos initFilter - subscribe (updates (fromRef urlHashRef)) $ - modifyRef todosRef . (\v s -> s{filter=v}) . parseFilter' - return todosRef - liftIO $ onBeforeUnload do - TodoListState{items} <- readRef todosRef - localStorageSet $ LocalStorageTodoItems items - return todosRef - ToggleAllAction cfg isChecked -> - modifyRef cfg.state_ref \s -> s - { items = - fmap (\i -> i {TodoItem.completed = isChecked}) s.items - } - InputAction cfg newVal -> do - modifyRef cfg.state_ref \s -> s {title = newVal} - CommitAction cfg -> do - title <- JSS.strip . (.title) <$> readRef cfg.state_ref - case title of - "" -> return () - t -> modifyRef cfg.state_ref \s -> s - { items = s.items <> [mkNewItem t] - , title = "" - } - KeydownAction cfg key -> case key of - 13 {- Enter -} -> eval (CommitAction cfg) - _ -> return () - DeleteItemAction cfg itemIx -> - modifyRef cfg.state_ref \s -> s {items = deleteIx itemIx s.items} - ClearCompletedAction cfg -> - modifyRef cfg.state_ref \s -> s - {items = (List.filter (not . TodoItem.completed)) s.items} - where - deleteIx :: Int -> [a] -> [a] - deleteIx _ [] = [] - deleteIx i (a:as) - | i == 0 = as - | otherwise = a : deleteIx (i-1) as - mkNewItem t = - TodoItem.emptyTodoItemState {TodoItem.title = t} - -html :: TodoListConfig -> Html () -html cfg = do - el "style" $ text styles - div_ do - section_ [class_ "todoapp"] do - headerWidget - mainWidget - footerWidget - footerInfoWidget - where - headerWidget = header_ [class_ "header"] do - h1_ (text "todos") - input_ [class_ "new-todo", placeholder_ "What needs to be done?", autofocus_ True] do - dynValue $ (.title) <$> fromRef cfg.state_ref - on "input" $ decodeEvent valueDecoder $ - eval . InputAction cfg - on "keydown" $ decodeEvent keyCodeDecoder $ - eval . KeydownAction cfg - mainWidget = section_ [class_ "main"] do - toggleClass "hidden" hiddenDyn - input_ [id_ "toggle-all", class_ "toggle-all", type_ "checkbox"] do - on "click" $ decodeEvent checkedDecoder $ - eval . ToggleAllAction cfg - label_ do - attr "for" "toggle-all" - text "Mark all as completed" - ul_ [class_ "todo-list"] do - simpleList itemsDyn \idx todoRef -> - TodoItem.html $ TodoItem.TodoItemConfig - { TodoItem.state_ref = todoRef - { dynref_modifier = todoItemModifier cfg idx todoRef.dynref_modifier - } - , TodoItem.is_hidden_dyn = - isTodoItemHidden <$> fromRef cfg.state_ref <*> fromRef todoRef - , TodoItem.ask_delete_item = eval (DeleteItemAction cfg idx) - } - footerWidget = footer_ [class_ "footer"] do - toggleClass "hidden" hiddenDyn - span_ [class_ "todo-count"] do - strong_ $ dynText $ JSS.pack . show <$> itemsLeftDyn - dynText $ pluralize " item left" " items left" <$> itemsLeftDyn - ul_ [class_ "filters"] do - for_ [All, Active, Completed] filterWidget - button_ [class_ "clear-completed"] do - on_ "click" $ eval (ClearCompletedAction cfg) - text "Clear completed" - footerInfoWidget = footer_ [class_ "info"] do - p_ "Double-click to edit a todo" - p_ do - text "Created by " - a_ [href_ "https://github.com/lagunoff"] "Vlad Lagunov" - p_ do - text "Part of " - a_ [href_ "http://todomvc.com"] "TodoMVC" - filterWidget flt = li_ do - a_ [href_ (printFilter flt)] do - toggleClass "selected" $ filterSelectedDyn flt - text $ JSS.pack (show flt) - hiddenDyn = - Prelude.null . (.items) <$> fromRef cfg.state_ref - itemsLeftDyn = - countItemsLeft <$> fromRef cfg.state_ref - filterSelectedDyn flt = - (==flt) . (.filter) <$> fromRef cfg.state_ref - itemsDyn = - (.items) <$> fromRef cfg.state_ref - countItemsLeft TodoListState{items} = - foldl (\acc TodoItem.TodoItemState{completed} -> - if not completed then acc + 1 else acc) 0 items - isTodoItemHidden listState itemState = - case (listState.filter, itemState.completed) of - (Active, True) -> True - (Completed, False) -> True - _ -> False - --- | Synchronize changes inside TodoItem widget with the outer --- TodoList widget. -todoItemModifier - :: TodoListConfig - -> Int - -> Modifier TodoItem.TodoItemState - -> Modifier TodoItem.TodoItemState -todoItemModifier cfg idx elemModifier = Modifier \upd f -> do - -- Update the local TodoItem element widget - ((old, new), result) <- unModifier elemModifier upd \old -> - let (new, result) = f old in (new, ((old, new), result)) - let - -- When False, the update event won't be propagated into the outer - -- widget for the sake of optimization - needsUpdate = upd && (old.completed /= new.completed) - -- Update the outer widget - unModifier (dynref_modifier cfg.state_ref) needsUpdate \old -> - (old {items = overIx idx (const new) old.items}, ()) - return result - where - overIx :: Int -> (a -> a) -> [a] -> [a] - overIx 0 f (x:xs) = f x : xs - overIx n f (x:xs) = x : overIx (pred n) f xs - overIx n _ [] = [] - -pluralize :: JSString -> JSString -> Int -> JSString -pluralize singular plural 0 = singular -pluralize singular plural _ = plural - -parseFilter :: JSString -> Maybe Filter -parseFilter = \case - "#/" -> Just All - "#/active" -> Just Active - "#/completed" -> Just Completed - _ -> Nothing - -printFilter :: Filter -> JSString -printFilter = \case - All -> "#/" - Active -> "#/active" - Completed -> "#/completed" - -styles :: JSString -styles = "\ - \body {\ - \ margin: 0;\ - \ padding: 0;\ - \}\ - \\ - \button {\ - \ margin: 0;\ - \ padding: 0;\ - \ border: 0;\ - \ background: none;\ - \ font-size: 100%;\ - \ vertical-align: baseline;\ - \ font-family: inherit;\ - \ font-weight: inherit;\ - \ color: inherit;\ - \ -webkit-appearance: none;\ - \ appearance: none;\ - \ -webkit-font-smoothing: antialiased;\ - \ -moz-osx-font-smoothing: grayscale;\ - \}\ - \\ - \body {\ - \ font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\ - \ line-height: 1.4em;\ - \ background: #f5f5f5;\ - \ color: #4d4d4d;\ - \ min-width: 230px;\ - \ max-width: 550px;\ - \ margin: 0 auto;\ - \ -webkit-font-smoothing: antialiased;\ - \ -moz-osx-font-smoothing: grayscale;\ - \ font-weight: 300;\ - \}\ - \\ - \:focus {\ - \ outline: 0;\ - \}\ - \\ - \.hidden {\ - \ display: none;\ - \}\ - \\ - \.todoapp {\ - \ background: #fff;\ - \ margin: 130px 0 40px 0;\ - \ position: relative;\ - \ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),\ - \ 0 25px 50px 0 rgba(0, 0, 0, 0.1);\ - \}\ - \\ - \.todoapp input::-webkit-input-placeholder {\ - \ font-style: italic;\ - \ font-weight: 300;\ - \ color: #e6e6e6;\ - \}\ - \\ - \.todoapp input::-moz-placeholder {\ - \ font-style: italic;\ - \ font-weight: 300;\ - \ color: #e6e6e6;\ - \}\ - \\ - \.todoapp input::input-placeholder {\ - \ font-style: italic;\ - \ font-weight: 300;\ - \ color: #e6e6e6;\ - \}\ - \\ - \.todoapp h1 {\ - \ position: absolute;\ - \ top: -155px;\ - \ width: 100%;\ - \ font-size: 100px;\ - \ font-weight: 100;\ - \ text-align: center;\ - \ color: rgba(175, 47, 47, 0.15);\ - \ -webkit-text-rendering: optimizeLegibility;\ - \ -moz-text-rendering: optimizeLegibility;\ - \ text-rendering: optimizeLegibility;\ - \}\ - \\ - \.new-todo,\ - \.edit {\ - \ position: relative;\ - \ margin: 0;\ - \ width: 100%;\ - \ font-size: 24px;\ - \ font-family: inherit;\ - \ font-weight: inherit;\ - \ line-height: 1.4em;\ - \ border: 0;\ - \ color: inherit;\ - \ padding: 6px;\ - \ border: 1px solid #999;\ - \ box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);\ - \ box-sizing: border-box;\ - \ -webkit-font-smoothing: antialiased;\ - \ -moz-osx-font-smoothing: grayscale;\ - \}\ - \\ - \.new-todo {\ - \ padding: 16px 16px 16px 60px;\ - \ border: none;\ - \ background: rgba(0, 0, 0, 0.003);\ - \ box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);\ - \}\ - \\ - \.main {\ - \ position: relative;\ - \ z-index: 2;\ - \ border-top: 1px solid #e6e6e6;\ - \}\ - \\ - \.toggle-all {\ - \ width: 1px;\ - \ height: 1px;\ - \ border: none; /* Mobile Safari */\ - \ opacity: 0;\ - \ position: absolute;\ - \ right: 100%;\ - \ bottom: 100%;\ - \}\ - \\ - \.toggle-all + label {\ - \ width: 60px;\ - \ height: 34px;\ - \ font-size: 0;\ - \ position: absolute;\ - \ top: -52px;\ - \ left: -13px;\ - \ -webkit-transform: rotate(90deg);\ - \ transform: rotate(90deg);\ - \}\ - \\ - \.toggle-all + label:before {\ - \ content: '❯';\ - \ font-size: 22px;\ - \ color: #e6e6e6;\ - \ padding: 10px 27px 10px 27px;\ - \}\ - \\ - \.toggle-all:checked + label:before {\ - \ color: #737373;\ - \}\ - \\ - \.todo-list {\ - \ margin: 0;\ - \ padding: 0;\ - \ list-style: none;\ - \}\ - \\ - \.todo-list li {\ - \ position: relative;\ - \ font-size: 24px;\ - \ border-bottom: 1px solid #ededed;\ - \}\ - \\ - \.todo-list li:last-child {\ - \ border-bottom: none;\ - \}\ - \\ - \.todo-list li.editing {\ - \ border-bottom: none;\ - \ padding: 0;\ - \}\ - \\ - \.todo-list li.editing .edit {\ - \ display: block;\ - \ width: calc(100% - 43px);\ - \ padding: 12px 16px;\ - \ margin: 0 0 0 43px;\ - \}\ - \\ - \.todo-list li.editing .view {\ - \ display: none;\ - \}\ - \\ - \.todo-list li .toggle {\ - \ text-align: center;\ - \ width: 40px;\ - \ /* auto, since non-WebKit browsers doesn't support input styling */\ - \ height: auto;\ - \ position: absolute;\ - \ top: 0;\ - \ bottom: 0;\ - \ margin: auto 0;\ - \ border: none; /* Mobile Safari */\ - \ -webkit-appearance: none;\ - \ appearance: none;\ - \}\ - \\ - \.todo-list li .toggle {\ - \ opacity: 0;\ - \}\ - \\ - \.todo-list li .toggle + label {\ - \ /*\ - \ Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433\ - \ IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/\ - \ */\ - \ background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');\ - \ background-repeat: no-repeat;\ - \ background-position: center left;\ - \}\ - \\ - \.todo-list li .toggle:checked + label {\ - \ background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');\ - \}\ - \\ - \.todo-list li label {\ - \ word-break: break-all;\ - \ padding: 15px 15px 15px 60px;\ - \ display: block;\ - \ line-height: 1.2;\ - \ transition: color 0.4s;\ - \}\ - \\ - \.todo-list li.completed label {\ - \ color: #d9d9d9;\ - \ text-decoration: line-through;\ - \}\ - \\ - \.todo-list li .destroy {\ - \ display: none;\ - \ position: absolute;\ - \ top: 0;\ - \ right: 10px;\ - \ bottom: 0;\ - \ width: 40px;\ - \ height: 40px;\ - \ margin: auto 0;\ - \ font-size: 30px;\ - \ color: #cc9a9a;\ - \ margin-bottom: 11px;\ - \ transition: color 0.2s ease-out;\ - \}\ - \\ - \.todo-list li .destroy:hover {\ - \ color: #af5b5e;\ - \}\ - \\ - \.todo-list li .destroy:after {\ - \ content: '×';\ - \}\ - \\ - \.todo-list li:hover .destroy {\ - \ display: block;\ - \}\ - \\ - \.todo-list li .edit {\ - \ display: none;\ - \}\ - \\ - \.todo-list li.editing:last-child {\ - \ margin-bottom: -1px;\ - \}\ - \\ - \.footer {\ - \ color: #777;\ - \ padding: 10px 15px;\ - \ height: 20px;\ - \ text-align: center;\ - \ border-top: 1px solid #e6e6e6;\ - \}\ - \\ - \.footer:before {\ - \ content: '';\ - \ position: absolute;\ - \ right: 0;\ - \ bottom: 0;\ - \ left: 0;\ - \ height: 50px;\ - \ overflow: hidden;\ - \ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),\ - \ 0 8px 0 -3px #f6f6f6,\ - \ 0 9px 1px -3px rgba(0, 0, 0, 0.2),\ - \ 0 16px 0 -6px #f6f6f6,\ - \ 0 17px 2px -6px rgba(0, 0, 0, 0.2);\ - \}\ - \\ - \.todo-count {\ - \ float: left;\ - \ text-align: left;\ - \}\ - \\ - \.todo-count strong {\ - \ font-weight: 300;\ - \}\ - \\ - \.filters {\ - \ margin: 0;\ - \ padding: 0;\ - \ list-style: none;\ - \ position: absolute;\ - \ right: 0;\ - \ left: 0;\ - \}\ - \\ - \.filters li {\ - \ display: inline;\ - \}\ - \\ - \.filters li a {\ - \ color: inherit;\ - \ margin: 3px;\ - \ padding: 3px 7px;\ - \ text-decoration: none;\ - \ border: 1px solid transparent;\ - \ border-radius: 3px;\ - \}\ - \\ - \.filters li a:hover {\ - \ border-color: rgba(175, 47, 47, 0.1);\ - \}\ - \\ - \.filters li a.selected {\ - \ border-color: rgba(175, 47, 47, 0.2);\ - \}\ - \\ - \.clear-completed,\ - \html .clear-completed:active {\ - \ float: right;\ - \ position: relative;\ - \ line-height: 20px;\ - \ text-decoration: none;\ - \ cursor: pointer;\ - \}\ - \\ - \.clear-completed:hover {\ - \ text-decoration: underline;\ - \}\ - \\ - \.info {\ - \ margin: 65px auto 0;\ - \ color: #bfbfbf;\ - \ font-size: 10px;\ - \ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\ - \ text-align: center;\ - \}\ - \\ - \.info p {\ - \ line-height: 1;\ - \}\ - \\ - \.info a {\ - \ color: inherit;\ - \ text-decoration: none;\ - \ font-weight: 400;\ - \}\ - \\ - \.info a:hover {\ - \ text-decoration: underline;\ - \}\ - \\ - \/*\ - \ Hack to remove background from Mobile Safari.\ - \ Can't use it globally since it destroys checkboxes in Firefox\ - \*/\ - \@media screen and (-webkit-min-device-pixel-ratio:0) {\ - \ .toggle-all,\ - \ .todo-list li .toggle {\ - \ background: none;\ - \ }\ - \\ - \ .todo-list li .toggle {\ - \ height: 40px;\ - \ }\ - \}\ - \\ - \@media (max-width: 430px) {\ - \ .footer {\ - \ height: 50px;\ - \ }\ - \\ - \ .filters {\ - \ bottom: 10px;\ - \ }\ - \}" diff --git a/examples/todomvc/Utils.hs b/examples/todomvc/Utils.hs deleted file mode 100644 index ba9e0b5..0000000 --- a/examples/todomvc/Utils.hs +++ /dev/null @@ -1,83 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE ForeignFunctionInterface #-} -{-# LANGUAGE JavaScriptFFI #-} -module Utils where - -import Control.Monad -import Control.Monad.Reader -import Data.Coerce -import Data.Typeable -import HtmlT -import JavaScript.Compat.Foreign.Callback -import JavaScript.Compat.Marshal -import JavaScript.Compat.Prim -import JavaScript.Compat.String (JSString(..)) -import JavaScript.Compat.String qualified as JSS -import Unsafe.Coerce - -mkUrlHashRef :: MonadReactive m => m (DynRef JSString) -mkUrlHashRef = do - initial <- liftIO js_readUrlHash - routeRef <- newRef initial - win <- getCurrentWindow - popStateCb <- liftIO $ asyncCallback $ - js_readUrlHash >>= dynStep . writeRef routeRef - liftIO $ js_setProp (coerce win) "onpopstate" (unsafeCoerce popStateCb) - return routeRef - -localStorageSet :: forall a m. (MonadIO m, ToJSVal a, Typeable a) => a -> m () -localStorageSet val = - liftIO (toJSVal val >>= js_setItem key) - where - key = JSS.pack . show $ typeRepFingerprint $ typeRep (Proxy @a) - -localStorageGet :: forall a m. (MonadIO m, FromJSVal a, Typeable a) => m (Maybe a) -localStorageGet = liftIO do - mval <- nullableToMaybe <$> (js_getItem key) - join <$> forM mval fromJSVal - where - key = JSS.pack . show . typeRepFingerprint $ typeRep (Proxy @a) - -#if defined(javascript_HOST_ARCH) -foreign import javascript unsafe - "(($1) => { setTimeout(function() {\ - var inputEl = $1.parentNode.parentNode.querySelector('input.edit');\ - inputEl.focus();\ - }, 0); })" - js_todoItemInputFocus :: JSVal -> IO () - -foreign import javascript unsafe - "(function(key, val){\ - localStorage.setItem(key, JSON.stringify(val));\ - })" - js_setItem :: JSString -> JSVal -> IO () - -foreign import javascript unsafe - "(function(key){\ - var itemText = localStorage.getItem(key);\ - return itemText ? JSON.parse(itemText) : null;\ - })" - js_getItem :: JSString -> IO (Nullable JSVal) -foreign import javascript unsafe - "(function(){\ - return location.hash;\ - })" - js_readUrlHash :: IO JSString - --- Need this because GHC.JS.Prim.Internal.Build buildObjectI3 is --- broken. The FFI declarations were not migrated from GHCJS style --- properly, they throw ReferenceError: $1 is not defined -foreign import javascript unsafe - "(function(k1, v1, k2, v2, k3, v3){\ - var res = {};\ - res[k1] = v1; res[k2] = v2; res[k3] = v3;\ - return res;\ - })" - js_buildObjectI3 :: JSVal -> JSVal -> JSVal -> JSVal -> JSVal -> JSVal -> JSVal -#else -js_todoItemInputFocus :: JSVal -> IO () = errorGhcjsOnly -js_setItem :: JSString -> JSVal -> IO () = errorGhcjsOnly -js_getItem :: JSString -> IO (Nullable JSVal) = errorGhcjsOnly -js_readUrlHash :: IO JSString = errorGhcjsOnly -js_buildObjectI3 :: JSVal -> JSVal -> JSVal -> JSVal -> JSVal -> JSVal -> JSVal = errorGhcjsOnly -#endif diff --git a/examples/todomvc/todomvc.hs b/examples/todomvc/todomvc.hs deleted file mode 100644 index a8c800e..0000000 --- a/examples/todomvc/todomvc.hs +++ /dev/null @@ -1,13 +0,0 @@ -import Control.Monad -import Control.Monad.Reader -import HtmlT - -import "this" TodoList qualified as TodoList -import "this" Utils - -main :: IO () -main = void $ attachToBody do - renv <- asks (.html_reactive_env) - urlHashRef <- mkUrlHashRef - todosRef <- dynStep $ TodoList.eval (TodoList.InitAction renv urlHashRef) - TodoList.html $ TodoList.TodoListConfig todosRef diff --git a/ghc-wasm-meta b/ghc-wasm-meta new file mode 160000 index 0000000..426e46a --- /dev/null +++ b/ghc-wasm-meta @@ -0,0 +1 @@ +Subproject commit 426e46a5a8ffda63a5dde02e0bf9eb60f9cca089 diff --git a/htmlt.cabal b/htmlt.cabal index 545cc7c..7eafc05 100644 --- a/htmlt.cabal +++ b/htmlt.cabal @@ -29,7 +29,7 @@ common htmlt-common -Wall -Wno-missing-signatures -Wno-name-shadowing -Wno-unused-matches -Wno-unused-do-bind if flag(production) - ghc-options: -O2 + ghc-options: -O3 default-extensions: AllowAmbiguousTypes BangPatterns @@ -78,91 +78,127 @@ common htmlt-common library import: htmlt-common - hs-source-dirs: src + hs-source-dirs: src ./examples/charts exposed-modules: - HtmlT - HtmlT.Base - HtmlT.DOM - HtmlT.Element - HtmlT.Event - HtmlT.Internal - HtmlT.Main - HtmlT.Property - HtmlT.Types - JavaScript.Compat.Foreign.Callback - JavaScript.Compat.Marshal - JavaScript.Compat.Prim - JavaScript.Compat.String + -- HtmlT + -- HtmlT.Base + -- HtmlT.DOM + -- HtmlT.Element + -- HtmlT.Event + -- HtmlT.Internal + -- HtmlT.Main + -- HtmlT.Property + -- HtmlT.Types + Clickable + Clickable.Core + Clickable.DOM + Clickable.Element + Clickable.Property + Clickable.Main + Clickable.Types + Clickable.Internal + Clickable.FFI + Wasm.Compat.Prim + Wasm.Compat.Marshal other-modules: Paths_htmlt build-depends: base, + binary, + bytestring, containers, exceptions, mtl, text, transformers, - if !arch(javascript) - build-depends: - ghc-prim, - other-modules: - JavaScript.Compat.String.Native - else - other-modules: - JavaScript.Compat.String.JavaScript + ghc-experimental, + ghc-prim, + -- generic-lens, -executable htmlt-counter - import: htmlt-common - main-is: counter.hs - hs-source-dirs: ./examples/counter - build-depends: - base, - htmlt, - transformers, - if !flag(examples) - buildable: False +-- executable htmlt-counter +-- import: htmlt-common +-- main-is: counter.hs +-- hs-source-dirs: ./examples/counter +-- ghc-options: +-- -no-hs-main -optl-mexec-model=reactor +-- -optl-Wl,--export=hs_init,--export=wasm_main +-- build-depends: +-- base, +-- htmlt, +-- text, +-- mtl, +-- transformers, +-- if !flag(examples) +-- buildable: False + +-- executable htmlt-todomvc +-- import: htmlt-common +-- main-is: todomvc.hs +-- hs-source-dirs: ./examples/todomvc +-- ghc-options: +-- -no-hs-main -optl-mexec-model=reactor +-- -optl-Wl,--export=hs_init,--export=wasm_main +-- other-modules: +-- TodoItem +-- TodoList +-- Utils +-- build-depends: +-- base, +-- htmlt, +-- text, +-- mtl, +-- if !flag(examples) +-- buildable: False -executable htmlt-todomvc +executable htmlt-charts import: htmlt-common - main-is: todomvc.hs - hs-source-dirs: ./examples/todomvc - other-modules: - TodoItem - TodoList - Utils + main-is: charts.hs + hs-source-dirs: ./examples/charts + ghc-options: + -no-hs-main -optl-mexec-model=reactor + -optl-Wl,--export=hs_init,--export=wasm_main build-depends: base, htmlt, + text, mtl, - if !flag(examples) - buildable: False - -executable htmlt-simple-routing - import: htmlt-common - main-is: simple-routing.hs - hs-source-dirs: ./examples/simple-routing other-modules: - Assets - Router - Pages - Utils - build-depends: - base, - bytestring, - mtl, - htmlt, + App + Charts + PairSelector if !flag(examples) buildable: False -executable htmlt-benchmarks - import: htmlt-common - main-is: benchmarks.hs - hs-source-dirs: ./benchmarks/ - build-depends: - base, - htmlt, - text, - gauge, - -- Fails to build with javascript-backend - buildable: False +-- executable htmlt-simple-routing +-- import: htmlt-common +-- main-is: simple-routing.hs +-- hs-source-dirs: ./examples/simple-routing +-- ghc-options: +-- -no-hs-main -optl-mexec-model=reactor +-- -optl-Wl,--export=hs_init,--export=wasm_main +-- other-modules: +-- Assets +-- Router +-- Pages +-- Utils +-- build-depends: +-- base, +-- bytestring, +-- mtl, +-- htmlt, +-- text, +-- if !flag(examples) +-- buildable: False + +-- executable htmlt-benchmarks +-- import: htmlt-common +-- main-is: benchmarks.hs +-- hs-source-dirs: ./benchmarks/ +-- build-depends: +-- base, +-- htmlt, +-- text, +-- gauge, +-- -- Fails to build with javascript-backend +-- buildable: False diff --git a/jsbits/index.js b/jsbits/index.js new file mode 100644 index 0000000..02d3edf --- /dev/null +++ b/jsbits/index.js @@ -0,0 +1,23 @@ +import { WASI, File, OpenFile } from '@bjorn3/browser_wasi_shim'; +import * as jsffi from './jsffi'; + +let __exports = {}; + +window.startReactor = async function (wasmUri, opt) { + const wasi = new WASI([], [], [ + new OpenFile(new File([])), // stdin + new OpenFile(new File([])), // stdout + new OpenFile(new File([])), // stderr + ]); + + const wasm = await WebAssembly.compileStreaming(fetch(wasmUri)); + const inst = await WebAssembly.instantiate(wasm, { + wasi_snapshot_preview1: wasi.wasiImport, + ghc_wasm_jsffi: jsffi.default(__exports) + }); + + Object.assign(__exports, inst.exports); + await wasi.initialize(inst); + await inst.exports.hs_init(); + await inst.exports.wasm_main(); +}; diff --git a/jsbits/jsffi.js b/jsbits/jsffi.js new file mode 100644 index 0000000..9c3c9f2 --- /dev/null +++ b/jsbits/jsffi.js @@ -0,0 +1,124 @@ +// This file implements the JavaScript runtime logic for Haskell +// modules that use JSFFI. It is not an ESM module, but the template +// of one; the post-linker script will copy all contents into a new +// ESM module. + +// Manage a mapping from unique 32-bit ids to actual JavaScript +// values. +class JSValManager { + #lastk = 0; + #kv = new Map(); + + constructor() {} + + // Maybe just bump this.#lastk? For 64-bit ids that's sufficient, + // but better safe than sorry in the 32-bit case. + #allocKey() { + let k = this.#lastk; + while (true) { + if (!this.#kv.has(k)) { + this.#lastk = k; + return k; + } + k = (k + 1) | 0; + } + } + + newJSVal(v) { + const k = this.#allocKey(); + this.#kv.set(k, v); + return k; + } + + // A separate has() call to ensure we can store undefined as a value + // too. Also, unconditionally check this since the check is cheap + // anyway, if the check fails then there's a use-after-free to be + // fixed. + getJSVal(k) { + if (!this.#kv.has(k)) { + throw new WebAssembly.RuntimeError(`getJSVal(${k})`); + } + return this.#kv.get(k); + } + + // Check for double free as well. + freeJSVal(k) { + if (!this.#kv.delete(k)) { + throw new WebAssembly.RuntimeError(`freeJSVal(${k})`); + } + } +} + +// A simple & fast setImmediate() implementation for browsers. It's +// not a drop-in replacement for node.js setImmediate() because: +// 1. There's no clearImmediate(), and setImmediate() doesn't return +// anything +// 2. There's no guarantee that callbacks scheduled by setImmediate() +// are executed in the same order (in fact it's the opposite lol), +// but you are never supposed to rely on this assumption anyway +class SetImmediate { + #fs = []; + #mc = new MessageChannel(); + + constructor() { + this.#mc.port1.addEventListener("message", () => { + this.#fs.pop()(); + }); + this.#mc.port1.start(); + } + + setImmediate(cb, ...args) { + this.#fs.push(() => cb(...args)); + this.#mc.port2.postMessage(undefined); + } +} + +// The actual setImmediate() to be used. This is a ESM module top +// level binding and doesn't pollute the globalThis namespace. +let setImmediate; +if (globalThis.setImmediate) { + // node.js, bun + setImmediate = globalThis.setImmediate; +} else { + /* try { + * // deno + * setImmediate = (await import("node:timers")).setImmediate; + * } catch { */ + // browsers + const sm = new SetImmediate(); + setImmediate = (cb, ...args) => sm.setImmediate(cb, ...args); + /* } */ +} + +export default (__exports) => { +const __ghc_wasm_jsffi_jsval_manager = new JSValManager(); +const __ghc_wasm_jsffi_finalization_registry = new FinalizationRegistry(sp => __exports.rts_freeStablePtr(sp)); +return { +newJSVal: (v) => __ghc_wasm_jsffi_jsval_manager.newJSVal(v), +getJSVal: (k) => __ghc_wasm_jsffi_jsval_manager.getJSVal(k), +freeJSVal: (k) => __ghc_wasm_jsffi_jsval_manager.freeJSVal(k), +scheduleWork: () => setImmediate(__exports.rts_schedulerLoop), +ZC0ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1) => ($1()), +ZC1ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1,$2,$3,$4) => {var j = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3)); if ($1 instanceof Comment) { return eval(j)($1.parentNode, $4); } else { return eval(j)($1, $4); }}, +ZC3ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1) => {function isOpenBracket(node) {return node instanceof Comment && node.textContent == 'ContentBoundary {{'} function isCloseBracket(node) {return node instanceof Comment && node.textContent == '}}'} var iter = $1; var nestedCounter = 0; for (;;){ if (!iter.previousSibling || (nestedCounter == 0 && isOpenBracket(iter.previousSibling)) ) break; if (isCloseBracket(iter.previousSibling)) nestedCounter++; else if (isOpenBracket(iter.previousSibling)) nestedCounter--; iter.previousSibling.parentNode.removeChild(iter.previousSibling); }}, +ZC4ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1) => {var c1 = document.createComment('ContentBoundary {{'); var c2 = document.createComment('}}'); if ($1 instanceof Comment) { $1.parentNode.insertBefore(c1, $1); $1.parentNode.insertBefore(c2, $1); } else { $1.appendChild(c1); $1.appendChild(c2); } return c2;}, +ZC5ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1,$2) => {console.log(new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $1, $2)));}, +ZC7ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1) => ((a1) => __exports.ghczuwasmzujsffiZC6ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC($1,a1)), +ZC10ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: () => (document.body), +ZC13ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1,$2,$3,$4,$5) => {var k = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3)); var v = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $4, $5)); if ($1 instanceof Comment) { $1.parentNode[k] = v; } else { $1[k] = v; }}, +ZC14ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1,$2,$3) => {$1.nodeValue = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));}, +ZC15ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1,$2,$3) => {var t = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3)); var n = document.createElement(t); if ($1 instanceof Comment) { $1.parentNode.insertBefore(n, $1); } else { $1.appendChild(n); } return n;}, +ZC16ZChtmltzm0zi1zi0zi0zminplaceZCClickableziFFIZC: ($1,$2,$3) => {var c = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3)); var n = document.createTextNode(c); if ($1 instanceof Comment) { $1.parentNode.insertBefore(n, $1); } else { $1.appendChild(n); } return n;}, +ZC0ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1,$2) => ($1.reject(new WebAssembly.RuntimeError($2))), +ZC19ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1) => ($1.resolve()), +ZC20ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: () => {let res, rej; const p = new Promise((resolve, reject) => { res = resolve; rej = reject; }); p.resolve = res; p.reject = rej; return p;}, +ZC21ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1,$2) => (__ghc_wasm_jsffi_finalization_registry.register($1, $2, $1)), +ZC0ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1) => (`${$1.stack ? $1.stack : $1}`), +ZC1ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1,$2) => ((new TextDecoder('utf-8', {fatal: true})).decode(new Uint8Array(__exports.memory.buffer, $1, $2))), +ZC2ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1,$2,$3) => ((new TextEncoder()).encodeInto($1, new Uint8Array(__exports.memory.buffer, $2, $3)).written), +ZC3ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1) => ($1.length), +ZC4ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1) => {if (!__ghc_wasm_jsffi_finalization_registry.unregister($1)) { throw new WebAssembly.RuntimeError('js_callback_unregister'); }}, +ZC0ZCghczminternalZCGHCziInternalziWasmziPrimziConcziInternalZC: async ($1) => (new Promise(res => setTimeout(res, $1 / 1000))), +ZC18ZCghczminternalZCGHCziInternalziWasmziPrimziImportsZC: ($1,$2) => ($1.then(() => __exports.rts_promiseResolveUnit($2), err => __exports.rts_promiseReject($2, err))), +}; +}; diff --git a/shell.nix b/shell.nix index 8b5272f..82fb296 100644 --- a/shell.nix +++ b/shell.nix @@ -1 +1 @@ -(import ./default.nix {}).shell.javascript +(import ./default.nix {}).shell diff --git a/src/Clickable.hs b/src/Clickable.hs new file mode 100644 index 0000000..06325d1 --- /dev/null +++ b/src/Clickable.hs @@ -0,0 +1,10 @@ +module Clickable (module Exports) where + +import Clickable.DOM as Exports +import Clickable.Core as Exports +import Clickable.Element as Exports +import Clickable.Main as Exports +import Clickable.Property as Exports +import Clickable.Types as Exports +import Wasm.Compat.Marshal as Exports +import Wasm.Compat.Prim as Exports diff --git a/src/Clickable/Core.hs b/src/Clickable/Core.hs new file mode 100644 index 0000000..a4c0257 --- /dev/null +++ b/src/Clickable/Core.hs @@ -0,0 +1,184 @@ +module Clickable.Core where + +import Control.Monad +import Control.Monad.Reader +import Control.Monad.State +import Data.IORef +import Data.Map qualified as Map +import Data.Text (Text) + +import Clickable.FFI +import Clickable.Internal (reactive, reactive_) +import Clickable.Internal qualified as Internal +import Clickable.Types +import Wasm.Compat.Prim + + +launchHtmlM :: JSVal -> InternalEnv -> HtmlM a -> IO a +launchHtmlM root env = + flip runReaderT env . unClickM . trampoline . flip runReaderT root . unHtmlM + +launchClickM :: InternalEnv -> ClickM a -> IO a +launchClickM env = flip runReaderT env . unClickM . trampoline + +liftClickM :: ClickM a -> HtmlM a +liftClickM = HtmlM . lift + +--------------------------------------- +-- OPERATIONS OVER DYNAMIC VARIABLES -- +--------------------------------------- + +mapVar :: DynVar a -> (a -> b) -> DynVal b +mapVar var = MapVal (FromVar var) + +newVar :: a -> ClickM (DynVar a) +newVar a = do + ref <- liftIO $ newIORef a + state \s -> (DynVar (SourceId s.next_id) ref, s {next_id = s.next_id + 1}) + +readVal :: DynVal a -> ClickM a +readVal (ConstVal a) = pure a +readVal (FromVar (DynVar _ ref)) = liftIO $ readIORef ref +readVal (MapVal val f) = fmap f $ readVal val +readVal (SplatVal f a) = liftA2 ($) (readVal f) (readVal a) + +readVar :: DynVar a -> ClickM a +readVar (DynVar _ ref) = liftIO $ readIORef ref + +atomicModifyVar :: DynVar s -> (s -> (s, a)) -> ClickM a +atomicModifyVar var@(DynVar varId ref) f = do + (newVal, a) <- liftIO $ atomicModifyIORef' ref g + modify $ Internal.unsafeTrigger varId newVal + return a + where + g old = let (new, a) = f old in (new, (new, a)) + +modifyVar :: DynVar s -> (s -> s) -> ClickM () +modifyVar var f = atomicModifyVar var ((,()) . f) + +writeVar :: DynVar s -> s -> ClickM () +writeVar var s = modifyVar var $ const s + +subscribe :: DynVal a -> (a -> ClickM ()) -> ClickM () +subscribe val k = reactive_ $ Internal.subscribe val k + +------------------------- +-- RESOURCE MANAGEMENT -- +------------------------- + +newScope :: ClickM ResourceScope +newScope = reactive Internal.newScope + +freeScope :: Bool -> ResourceScope -> ClickM () +freeScope unlink s = f where + f = reactive (const (Internal.freeScope unlink s)) >>= g + g [] = return () + g ((_, ScopeFinalizer s'):xs) = freeScope True s' >> g xs + g ((_, CustomFinalizer x):xs) = x >> g xs + +installFinalizer :: ClickM () -> ClickM () +installFinalizer k = reactive_ $ Internal.installFinalizer k + +-- | Loop until transaction_queue is empty. +-- +-- This makes possible to implement @Applicative DynVal@ without +-- redundantly firing callback for the final result. TODO: Is this +-- even worth-while to have? What if just let multiple DOM changes +-- when it depends on multiple sources? +trampoline :: ClickM a -> ClickM a +trampoline act = loop0 act where + loop0 :: ClickM a -> ClickM a + loop0 before = do + r <- before + mcont <- popQueue + forM_ mcont loop1 + return r + loop1 :: ClickM () -> ClickM () + loop1 before = do + before + mcont <- popQueue + forM_ mcont loop1 + popQueue :: ClickM (Maybe (ClickM ())) + popQueue = state \s -> + case Map.minViewWithKey s.transaction_queue of + Nothing -> (Nothing, s) + Just ((_, r), newQueue) -> (Just r, s {transaction_queue = newQueue}) + +------------------ +-- BUILDING DOM -- +------------------ + +el :: Text -> HtmlM a -> HtmlM a +el t ch = do + r <- ask + el <- liftIO $ insertElement r t + local (const el) ch + +property :: Text -> Text -> HtmlM () +property k v = do + root <- ask + liftIO $ setProperty root k v + +boolProperty :: Text -> Bool -> HtmlM () +boolProperty k v = do + root <- ask + liftIO $ setBoolProperty root k v + +attribute :: Text -> Text -> HtmlM () +attribute k v = do + root <- ask + liftIO $ setAttribute root k v + +text :: Text -> HtmlM () +text t = do + r <- ask + void $ liftIO $ insertText r t + +dynText :: DynVal Text -> HtmlM () +dynText val = do + r <- ask + t <- liftClickM $ readVal val + textNode <- liftIO $ insertText r t + liftClickM $ subscribe val $ \new -> + liftIO $ updateTextContent textNode new + +dynProp :: Text -> DynVal Text -> HtmlM () +dynProp k val = do + root <- ask + v <- liftClickM $ readVal val + liftIO $ setProperty root k v + liftClickM $ subscribe val $ \new -> + liftIO $ setProperty root k new + +dynBoolProp :: Text -> DynVal Bool -> HtmlM () +dynBoolProp k val = do + root <- ask + v <- liftClickM $ readVal val + liftIO $ setBoolProperty root k v + liftClickM $ subscribe val $ \new -> + liftIO $ setBoolProperty root k new + +blank :: Applicative m => m () +blank = pure () + +--------------------- +-- DYNAMIC CONTENT -- +--------------------- + +dyn :: DynVal (HtmlM ()) -> HtmlM () +dyn val = do + root <- ask + scope <- liftClickM newScope + closeBracket <- liftIO $ js_insertBrackets root + initialVal <- liftClickM $ readVal val + let + update html = do + liftIO $ js_clearBrackets closeBracket + html + exec = local (\s -> s {scope}) . + flip runReaderT closeBracket . unHtmlM + liftClickM $ exec $ update initialVal + liftClickM $ subscribe val \newVal -> do + freeScope False scope + exec $ update newVal + return () diff --git a/src/Clickable/DOM.hs b/src/Clickable/DOM.hs new file mode 100644 index 0000000..57201fa --- /dev/null +++ b/src/Clickable/DOM.hs @@ -0,0 +1,240 @@ +module Clickable.DOM where + +import Control.Monad.Reader +import Data.Text (Text) +import Data.Kind +import GHC.Generics + +import Clickable.FFI qualified as FFI +import Clickable.Core +import Clickable.Types +import Wasm.Compat.Prim +import Wasm.Compat.Marshal + + +data EventListenerOptions = EventListenerOptions + { prevent_default :: Bool + , stop_propagation :: Bool + } deriving stock (Generic, Show, Eq) + +defaultEventListenerOptions :: EventListenerOptions +defaultEventListenerOptions = EventListenerOptions + { prevent_default = False + , stop_propagation = False + } + +addEventListener :: ConnectResourceArgs callback -> callback -> HtmlM () +addEventListener args k = do + rootElement <- ask + liftClickM $ connectResource rootElement args k + +data ConnectResourceArgs callback = ConnectResourceArgs + { js_wrapper :: UnsafeJavaScript + , mk_callback :: callback -> JSVal -> ClickM () + } + +connectResource :: JSVal -> ConnectResourceArgs callback -> callback -> ClickM () +connectResource target args k = do + e :: InternalEnv <- ask + let hsCallback' = (`runReaderT` e) . unClickM . trampoline . args.mk_callback k + hsCallback'' <- liftIO $ FFI.js_dynExport hsCallback' + cancel <- liftIO $ FFI.aquireResource target args.js_wrapper.unUnsafeJavaScript hsCallback'' + installFinalizer do + liftIO $ FFI.apply0 cancel + liftIO $ freeJSVal hsCallback'' + +on :: forall eventName. IsEventName eventName => EventListenerCb eventName -> HtmlM () +on k = addEventListener (addEventListenerArgs @eventName) k + +class IsEventName eventName where + type EventListenerCb eventName :: Type + addEventListenerArgs :: ConnectResourceArgs (EventListenerCb eventName) + +instance IsEventName "click" where + type EventListenerCb "click" = ClickM () + addEventListenerArgs = pointerConnectArgs "click" + +instance IsEventName "mousedown" where + type EventListenerCb "mousedown" = ClickM () + addEventListenerArgs = pointerConnectArgs "mousedown" + +instance IsEventName "mouseup" where + type EventListenerCb "mouseup" = ClickM () + addEventListenerArgs = pointerConnectArgs "mouseup" + +instance IsEventName "dblclick" where + type EventListenerCb "dblclick" = ClickM () + addEventListenerArgs = pointerConnectArgs "dblclick" + +instance IsEventName "submit" where + type EventListenerCb "submit" = ClickM () + addEventListenerArgs = submitConnectArgs + +instance IsEventName "input" where + type EventListenerCb "input" = Text -> ClickM () + addEventListenerArgs = inputConnectArgs "input" + +instance IsEventName "keydown" where + type EventListenerCb "keydown" = Int -> ClickM () + addEventListenerArgs = keyboardConnectArgs "keydown" + +instance IsEventName "keyup" where + type EventListenerCb "keyup" = Int -> ClickM () + addEventListenerArgs = keyboardConnectArgs "keyup" + +instance IsEventName "focus" where + type EventListenerCb "focus" = ClickM () + addEventListenerArgs = pointerConnectArgs "focus" + +instance IsEventName "blur" where + type EventListenerCb "blur" = ClickM () + addEventListenerArgs = pointerConnectArgs "blur" + +instance IsEventName "input/blur" where + type EventListenerCb "input/blur" = Text -> ClickM () + addEventListenerArgs = inputConnectArgs "blur" + +instance IsEventName "input/focus" where + type EventListenerCb "input/focus" = Text -> ClickM () + addEventListenerArgs = inputConnectArgs "focus" + +instance IsEventName "checkbox/change" where + type EventListenerCb "checkbox/change" = Bool -> ClickM () + addEventListenerArgs = checkboxChangeConnectArgs + +instance IsEventName "select/change" where + type EventListenerCb "select/change" = Text -> ClickM () + addEventListenerArgs = selectChangeConnectArgs + +-- https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event +pointerConnectArgs :: Text -> ConnectResourceArgs (ClickM ()) +pointerConnectArgs eventName = ConnectResourceArgs + { js_wrapper = normalEventWrapper eventName defaultEventListenerOptions + , mk_callback = \k _ -> k + } + +-- https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event +submitConnectArgs :: ConnectResourceArgs (ClickM ()) +submitConnectArgs = ConnectResourceArgs + { js_wrapper = normalEventWrapper "submit" EventListenerOptions + { prevent_default = True + , stop_propagation = True + } + , mk_callback = \k _ -> k + } + +-- https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event +inputConnectArgs :: Text -> ConnectResourceArgs (Text -> ClickM ()) +inputConnectArgs eventName = ConnectResourceArgs + { js_wrapper = + "(function(target, haskellCb){\n\ + \ function listener(target){\n\ + \ haskellCb(event.target.value);\n\ + \ }\n\ + \ window.addEventListener('" <> UnsafeJavaScript eventName <> "', listener);\n\ + \ return () => window.removeEventListener('" <> UnsafeJavaScript eventName <> "', listener);\n\ + \})" + , mk_callback = \k event -> liftIO (fromJSVal event) >>= mapM_ k + } + +-- https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event +-- https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event +keyboardConnectArgs :: Text -> ConnectResourceArgs (Int -> ClickM ()) +keyboardConnectArgs eventName = ConnectResourceArgs + { js_wrapper = + "(function(target, haskellCb){\n\ + \ function listener(target){\n\ + \ haskellCb(event.target.value);\n\ + \ }\n\ + \ window.addEventListener('" <> UnsafeJavaScript eventName <> "', listener);\n\ + \ return () => window.removeEventListener('" <> UnsafeJavaScript eventName <> "', listener);\n\ + \})" + , mk_callback = \k event -> liftIO (fromJSVal event) >>= mapM_ k + } + +-- https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event +-- https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event +-- https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event +-- https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event +focusConnectArgs :: Text -> ConnectResourceArgs (ClickM ()) +focusConnectArgs eventName = ConnectResourceArgs + { js_wrapper = normalEventWrapper eventName defaultEventListenerOptions + , mk_callback = \k _ -> k + } + +-- https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event +checkboxChangeConnectArgs :: ConnectResourceArgs (Bool -> ClickM ()) +checkboxChangeConnectArgs = ConnectResourceArgs + { js_wrapper = + "(function(target, haskellCb){\n\ + \ function listener(target){\n\ + \ haskellCb(event.target.checked);\n\ + \ }\n\ + \ window.addEventListener('change', listener);\n\ + \ return () => window.removeEventListener('change', listener);\n\ + \})" + , mk_callback = \k event -> liftIO (fromJSVal event) >>= mapM_ k + } + +-- https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event +selectChangeConnectArgs :: ConnectResourceArgs (Text -> ClickM ()) +selectChangeConnectArgs = ConnectResourceArgs + { js_wrapper = + "(function(target, haskellCb){\n\ + \ function listener(target){\n\ + \ haskellCb(event.target.value);\n\ + \ }\n\ + \ window.addEventListener('change', listener);\n\ + \ return () => window.removeEventListener('change', listener);\n\ + \})" + , mk_callback = \k event -> liftIO (fromJSVal event) >>= mapM_ k + } + +normalEventWrapper :: Text -> EventListenerOptions -> UnsafeJavaScript +normalEventWrapper eventName opt = + "(function(target, haskellCb){\n\ + \ function listener(event){\n\ + \ " <> preventDefaultStmt <> "\n\ + \ " <> stopPropagationStmt <> "\n\ + \ haskellCb(event);\n\ + \ }\n\ + \ target.addEventListener('" <> UnsafeJavaScript eventName <> "', listener);\n\ + \ return () => target.removeEventListener('" <> UnsafeJavaScript eventName <> "', listener);\n\ + \})" + where + preventDefaultStmt = if opt.prevent_default then "event.preventDefault();" else "" + stopPropagationStmt = if opt.stop_propagation then "event.stopPropagation();" else "" + +data Location = Location + { protocol :: Text + -- ^ A string containing the protocol scheme of the URL, including + -- the final ':' + , hostname :: Text + -- ^ A string containing the domain of the URL. + , port :: Text + -- ^ A string containing the port number of the URL. + , pathname :: Text + -- ^ A string containing an initial '/' followed by the path of the + -- URL, not including the query string or fragment. + , search :: Text + -- ^ A string containing a '?' followed by the parameters or + -- "querystring" of the URL + , hash :: Text + -- ^ A string containing a '#' followed by the fragment identifier + -- of the URL. + } deriving stock (Show, Eq, Generic) + deriving anyclass (FromJSVal, ToJSVal) + +-- https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event +popstateConnectArgs :: ConnectResourceArgs (Location -> ClickM ()) +popstateConnectArgs = ConnectResourceArgs + { js_wrapper = + "(function(target, haskellCb){\n\ + \ function listener(){\n\ + \ haskellCb(location);\n\ + \ }\n\ + \ target.addEventListener('popstate', listener);\n\ + \ return () => target.removeEventListener('popstate', listener);\n\ + \})" + , mk_callback = \k event -> liftIO (fromJSVal event) >>= mapM_ k + } diff --git a/src/HtmlT/Element.hs b/src/Clickable/Element.hs similarity index 97% rename from src/HtmlT/Element.hs rename to src/Clickable/Element.hs index c570c39..de84a7d 100644 --- a/src/HtmlT/Element.hs +++ b/src/Clickable/Element.hs @@ -1,11 +1,11 @@ {-| Shortcuts for most common HTML5 elements -} -module HtmlT.Element where +module Clickable.Element where -import HtmlT.Base -import HtmlT.Types -import JavaScript.Compat.String (JSString(..)) +import Clickable.Core +import Clickable.Types +import Data.Text -- | This typeclass allows for tag constructors to have variable -- length arguments. Each tag constructor like 'div_' defined below @@ -18,18 +18,18 @@ import JavaScript.Compat.String (JSString(..)) -- https://github.com/chrisdone/lucid/blob/fb3b0e7c189c2acd8d88838d4a13923f24542ee8/src/Lucid/Base.hs#L272 class Term arg result | result -> arg where term - :: JSString -- ^ Name. + :: Text -- ^ Name. -> arg -- ^ Some argument. -> result -- ^ Result: either an element or an attribute. -- | Given attributes, expect more child input. -instance f ~ Html a => Term [Html ()] (f -> Html a) where +instance f ~ HtmlM a => Term [HtmlM ()] (f -> HtmlM a) where term name attrs = el name . (sequence_ attrs *>) {-# INLINE term #-} -- | Given children immediately, just use that and expect no -- attributes. -instance Term (Html a) (Html a) where +instance Term (HtmlM a) (HtmlM a) where term = el {-# INLINE term #-} @@ -357,7 +357,7 @@ sup_ :: Term arg result => arg -> result sup_ = term "sup" {-# INLINE sup_ #-} -br_ :: Html () +br_ :: HtmlM () br_ = el "br" blank {-# INLINE br_ #-} diff --git a/src/Clickable/FFI.hs b/src/Clickable/FFI.hs new file mode 100644 index 0000000..37a15ba --- /dev/null +++ b/src/Clickable/FFI.hs @@ -0,0 +1,250 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE ForeignFunctionInterface #-} +{-# LANGUAGE MagicHash #-} + +#if defined(wasm32_HOST_ARCH) +{-# LANGUAGE JavaScriptFFI #-} +#endif +module Clickable.FFI where + +import Control.Monad.IO.Class +import Wasm.Compat.Prim +import GHC.Ptr +import Data.Word +import GHC.Prim +import Data.Text.Internal +import Data.Array.Byte + + +newtype DomBuilder = DomBuilder {unDomBuilder :: JSVal} + +insertText :: JSVal -> Text -> IO JSVal +insertText root (Text (ByteArray arr) off len) = do + let addr = byteArrayContents# arr + js_insertText root (Ptr addr `plusPtr` off) len + +insertElement :: JSVal -> Text -> IO JSVal +insertElement root (Text (ByteArray arr) off len) = do + let addr = byteArrayContents# arr + js_insertElement root (Ptr addr `plusPtr` off) len + +updateTextContent :: JSVal -> Text -> IO () +updateTextContent root (Text (ByteArray arr) off len) = do + let addr = byteArrayContents# arr + js_updateTextContent root (Ptr addr `plusPtr` off) len + +setProperty :: JSVal -> Text -> Text -> IO () +setProperty root (Text (ByteArray arr0) off0 len0) (Text (ByteArray arr1) off1 len1) = do + let addr0 = byteArrayContents# arr0 + let addr1 = byteArrayContents# arr1 + js_setProperty root (Ptr addr0 `plusPtr` off0) len0 (Ptr addr1 `plusPtr` off1) len1 + +setBoolProperty :: JSVal -> Text -> Bool -> IO () +setBoolProperty root (Text (ByteArray arr0) off0 len0) val = do + let addr0 = byteArrayContents# arr0 + js_setBoolProperty root (Ptr addr0 `plusPtr` off0) len0 (if val then 1 else 0) + +setAttribute :: JSVal -> Text -> Text -> IO () +setAttribute root (Text (ByteArray arr0) off0 len0) (Text (ByteArray arr1) off1 len1) = do + let addr0 = byteArrayContents# arr0 + let addr1 = byteArrayContents# arr1 + js_setAttribute root (Ptr addr0 `plusPtr` off0) len0 (Ptr addr1 `plusPtr` off1) len1 + +addEventListener :: JSVal -> Text -> JSVal -> IO () +addEventListener root (Text (ByteArray arr) off len) lisnr = do + let addr = byteArrayContents# arr + js_addEventListener root (Ptr addr `plusPtr` off) len lisnr + +removeEventListener :: JSVal -> Text -> JSVal -> IO () +removeEventListener root (Text (ByteArray arr) off len) lisnr = do + let addr = byteArrayContents# arr + js_removeEventListener root (Ptr addr `plusPtr` off) len lisnr + +consoleLog :: MonadIO m => Text -> m () +consoleLog (Text (ByteArray arr) off len) = liftIO do + let addr = byteArrayContents# arr + js_consoleLog (Ptr addr `plusPtr` off) len + +aquireResource :: JSVal -> Text -> JSVal -> IO JSVal +aquireResource root (Text (ByteArray arr) off len) lisnr = do + let addr = byteArrayContents# arr + js_aquireResource root (Ptr addr `plusPtr` off) len lisnr + +apply0 :: JSVal -> IO () +apply0 = js_apply0 + +#if !defined(wasm32_HOST_ARCH) +js_insertText :: JSVal -> Ptr Word8 -> Int -> IO JSVal +js_insertText = undefined + +js_insertElement :: JSVal -> Ptr Word8 -> Int -> IO JSVal +js_insertElement = undefined + +js_updateTextContent :: JSVal -> Ptr Word8 -> Int -> IO () +js_updateTextContent = undefined + +js_setProperty :: JSVal -> Ptr Word8 -> Int -> Ptr Word8 -> Int -> IO () +js_setProperty = undefined + +js_setBoolProperty :: JSVal -> Ptr Word8 -> Int -> Int -> IO () +js_setBoolProperty = undefined + +js_setAttribute :: JSVal -> Ptr Word8 -> Int -> Ptr Word8 -> Int -> IO () +js_setAttribute = undefined + +js_addEventListener :: JSVal -> Ptr Word8 -> Int -> JSVal -> IO () +js_addEventListener = undefined + +js_removeEventListener :: JSVal -> Ptr Word8 -> Int -> JSVal -> IO () +js_removeEventListener = undefined + +js_dynExport :: (JSVal -> IO ()) -> IO JSVal +js_dynExport = undefined + +documentBody :: IO JSVal +documentBody = undefined + +js_consoleLog :: Ptr Word8 -> Int -> IO () +js_consoleLog = undefined + +js_insertBrackets :: JSVal -> IO JSVal +js_insertBrackets = undefined + +js_clearBrackets :: JSVal -> IO () +js_clearBrackets = undefined + +js_removeBrackets :: JSVal -> IO () +js_removeBrackets = undefined + +js_aquireResource :: JSVal -> Ptr Word8 -> Int -> JSVal -> IO JSVal +js_aquireResource = undefined + +js_apply0 :: JSVal -> IO () +js_apply0 = undefined + +#else +foreign import javascript unsafe + "var c = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));\ + var n = document.createTextNode(c);\ + if ($1 instanceof Comment) {\ + $1.parentNode.insertBefore(n, $1);\ + } else {\ + $1.appendChild(n);\ + }\ + return n;" + js_insertText :: JSVal -> Ptr Word8 -> Int -> IO JSVal +foreign import javascript unsafe + "var t = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));\ + var n = document.createElement(t);\ + if ($1 instanceof Comment) {\ + $1.parentNode.insertBefore(n, $1);\ + } else {\ + $1.appendChild(n);\ + }\ + return n;" + js_insertElement :: JSVal -> Ptr Word8 -> Int -> IO JSVal +foreign import javascript unsafe + "$1.nodeValue = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));" + js_updateTextContent :: JSVal -> Ptr Word8 -> Int -> IO () +foreign import javascript unsafe + "var k = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));\ + var v = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $4, $5));\ + if ($1 instanceof Comment) {\ + $1.parentNode[k] = v;\ + } else {\ + $1[k] = v;\ + }" + js_setProperty :: JSVal -> Ptr Word8 -> Int -> Ptr Word8 -> Int -> IO () +foreign import javascript unsafe + "var k = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));\ + if ($1 instanceof Comment) {\ + $1.parentNode[k] = $4 == 0 ? false : true;\ + } else {\ + $1[k] = $4 == 0 ? false : true;\ + }" + js_setBoolProperty :: JSVal -> Ptr Word8 -> Int -> Int -> IO () +foreign import javascript unsafe + "var k = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));\ + var v = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $4, $5));\ + if ($1 instanceof Comment) {\ + $1.parentNode.setAttribute(k, v);\ + } else {\ + $1.setAttribute(k, v);\ + }" + js_setAttribute :: JSVal -> Ptr Word8 -> Int -> Ptr Word8 -> Int -> IO () +foreign import javascript unsafe "document.body" documentBody :: IO JSVal +foreign import javascript unsafe + "var e = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));\ + if ($1 instanceof Comment) {\ + $1.parentNode.addEventListener(e, $4);\ + } else {\ + $1.addEventListener(e, $4);\ + }" + js_addEventListener :: JSVal -> Ptr Word8 -> Int -> JSVal -> IO () +foreign import javascript unsafe + "var e = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));\ + if ($1 instanceof Comment) {\ + $1.parentNode.addEventListener(e, $4);\ + } else {\ + $1.removeEventListener(e, $4);\ + }" + js_removeEventListener :: JSVal -> Ptr Word8 -> Int -> JSVal -> IO () +foreign import javascript "wrapper" js_dynExport :: (JSVal -> IO ()) -> IO JSVal +foreign import javascript unsafe + "console.log(new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $1, $2)));" + js_consoleLog :: Ptr Word8 -> Int -> IO () +foreign import javascript unsafe + "var c1 = document.createComment('ContentBoundary {{');\ + var c2 = document.createComment('}}');\ + if ($1 instanceof Comment) {\ + $1.parentNode.insertBefore(c1, $1);\ + $1.parentNode.insertBefore(c2, $1);\ + } else {\ + $1.appendChild(c1);\ + $1.appendChild(c2);\ + }\ + return c2;" + js_insertBrackets :: JSVal -> IO JSVal +foreign import javascript unsafe + "function isOpenBracket(node) {return node instanceof Comment && node.textContent == 'ContentBoundary {{'}\ + function isCloseBracket(node) {return node instanceof Comment && node.textContent == '}}'}\ + var iter = $1;\ + var nestedCounter = 0;\ + for (;;){\ + if (!iter.previousSibling ||\ + (nestedCounter == 0 && isOpenBracket(iter.previousSibling))\ + ) break;\ + if (isCloseBracket(iter.previousSibling)) nestedCounter++;\ + else if (isOpenBracket(iter.previousSibling)) nestedCounter--;\ + iter.previousSibling.parentNode.removeChild(iter.previousSibling);\ + }" + js_clearBrackets :: JSVal -> IO () +foreign import javascript unsafe + "function isOpenBracket(node) {return node instanceof Comment && node.textContent == 'ContentBoundary {{'}\ + function isCloseBracket(node) {return node instanceof Comment && node.textContent == '}}'}\ + var iter = $1;\ + var nestedCounter = 0;\ + for (;;){\ + if (!iter.previousSibling ||\ + (nestedCounter == 0 && isOpenBracket(iter.previousSibling))\ + ) break;\ + if (isCloseBracket(iter.previousSibling)) nestedCounter++;\ + else if (isOpenBracket(iter.previousSibling)) nestedCounter--;\ + iter.previousSibling.parentNode.removeChild(iter.previousSibling);\ + }\ + $1.parentNode($1);\ + if ($1 != iter) iter.parentNode.removeChild(iter);\ + " + js_removeBrackets :: JSVal -> IO () +foreign import javascript unsafe + "var j = new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3));\ + if ($1 instanceof Comment) {\ + return eval(j)($1.parentNode, $4);\ + } else {\ + return eval(j)($1, $4);\ + }" + js_aquireResource :: JSVal -> Ptr Word8 -> Int -> JSVal -> IO JSVal +foreign import javascript unsafe + "$1()" js_apply0 :: JSVal -> IO () +#endif diff --git a/src/Clickable/Internal.hs b/src/Clickable/Internal.hs new file mode 100644 index 0000000..37f463f --- /dev/null +++ b/src/Clickable/Internal.hs @@ -0,0 +1,96 @@ +module Clickable.Internal where + +import Clickable.Types +import Control.Monad.Reader +import Control.Monad.State +import Data.IORef +import Data.List qualified as List +import Data.Map qualified as Map +import Unsafe.Coerce + + +emptyInternalState :: InternalState +emptyInternalState = InternalState [] [] Map.empty 0 + +newInternalEnv :: IO InternalEnv +newInternalEnv = do + let scope = ResourceScope emptyInternalState.next_id + internal_state_ref <- newIORef emptyInternalState + {next_id = emptyInternalState.next_id + 1} + return InternalEnv {internal_state_ref, scope} + +unsafeTrigger :: SourceId -> a -> InternalState -> InternalState +unsafeTrigger varId vals = go0 where + go0 = defer varId $ gets (.subscriptions) >>= go1 + go1 [] = return () + go1 ((_, sVar, cb) : xs) + | sVar == varId = cb (unsafeCoerce vals) >> go1 xs + | otherwise = go1 xs + +newScope :: ResourceScope -> InternalState -> (InternalState, ResourceScope) +newScope p s = + let + scopeId = ResourceScope s.next_id + finalizers = (p, ScopeFinalizer scopeId) : s.finalizers + next_id = s.next_id + 1 + in + (s {finalizers, next_id}, scopeId) + +freeScope :: + Bool -> + ResourceScope -> + InternalState -> (InternalState, [(ResourceScope, FinalizerVal)]) +freeScope unlink rscope s = + let + chkSub (s, _, _) = s /= rscope + chkFin True (s1, ScopeFinalizer s2) = s1 /= rscope && s2 /= rscope + chkFin True (s, _) = s /= rscope + chkFin False (s, _) = s /= rscope + (finalizers, scopeFns) = List.partition (chkFin unlink) s.finalizers + subscriptions = List.filter chkSub s.subscriptions + in + (s {subscriptions, finalizers}, scopeFns) + +installFinalizer :: ClickM () -> ResourceScope -> InternalState -> InternalState +installFinalizer k scope s = s + {finalizers = (scope, CustomFinalizer k) : s.finalizers} + +subscribe :: + DynVal a -> + (a -> ClickM ()) -> + ResourceScope -> + InternalState -> InternalState +subscribe (ConstVal _) _ _ s = s +subscribe (FromVar (DynVar varId _)) fn scope s = s {subscriptions} + where + subscriptions = newSub : s.subscriptions + newSub = (scope, varId, fn . unsafeCoerce) +subscribe (MapVal v f) fn scope s = subscribe v (fn . f) scope s +subscribe (SplatVal fv av) fn scope s = + subscribe av g scope $ subscribe fv f scope $ attachCb s + where + f fv' = do + av' <- readVal av + modify $ unsafeTrigger varid $ fv' av' + g av' = do + fv' <- readVal fv + modify $ unsafeTrigger varid $ fv' av' + attachCb s = s + { subscriptions = (scope, varid, fn . unsafeCoerce) : s.subscriptions + , next_id = s.next_id + 1 + } + varid = SourceId s.next_id + readVal :: DynVal a -> ClickM a + readVal (ConstVal a) = pure a + readVal (FromVar (DynVar _ ref)) = liftIO $ readIORef ref + readVal (MapVal val f) = fmap f $ readVal val + readVal (SplatVal f a) = liftA2 ($) (readVal f) (readVal a) + +defer :: SourceId -> ClickM () -> InternalState -> InternalState +defer k act s = s { transaction_queue = Map.insert k act s.transaction_queue } + +reactive :: (ResourceScope -> InternalState -> (InternalState, a)) -> ClickM a +reactive f = ClickM $ ReaderT $ \e -> atomicModifyIORef' e.internal_state_ref $ f e.scope + +reactive_ :: (ResourceScope -> InternalState -> InternalState) -> ClickM () +reactive_ f = reactive \scope s -> (f scope s, ()) diff --git a/src/Clickable/Main.hs b/src/Clickable/Main.hs new file mode 100644 index 0000000..4fc2816 --- /dev/null +++ b/src/Clickable/Main.hs @@ -0,0 +1,37 @@ +module Clickable.Main where + +import GHC.Generics +import Clickable.FFI +import Clickable.Core +import Clickable.Types +import Clickable.Internal (newInternalEnv) +import Wasm.Compat.Prim + +data AttachOptions = AttachOptions + { internal_env :: InternalEnv + , dom_builder :: JSVal + } deriving Generic + +-- | Needed to manually finalize and detach the application +data RunningApp = RunningApp + { internal_env :: InternalEnv + , dom_bracket :: JSVal + } deriving Generic + +attachWithOptions :: AttachOptions -> HtmlM a -> IO (a, RunningApp) +attachWithOptions opt app = do + domBracket <- js_insertBrackets opt.dom_builder + result <- launchHtmlM domBracket opt.internal_env app + let runApp = RunningApp opt.internal_env domBracket + return (result, runApp) + +attach :: HtmlM a -> IO (a, RunningApp) +attach html = do + body <- documentBody + internalEnv <- newInternalEnv + attachWithOptions (AttachOptions internalEnv body) html + +detach :: RunningApp -> IO () +detach app = do + launchClickM app.internal_env $ freeScope True app.internal_env.scope + js_removeBrackets app.dom_bracket diff --git a/src/Clickable/Property.hs b/src/Clickable/Property.hs new file mode 100644 index 0000000..1e20ccf --- /dev/null +++ b/src/Clickable/Property.hs @@ -0,0 +1,366 @@ +{-| +Shortcuts for common HTML5 attributes and properties +-} +module Clickable.Property where + +import Clickable.Core +import Clickable.Types +import Data.Text + + +-- TODO: Real-world usage has demonstrated that 'dynStyles' not +-- sufficiently composable. For instance, if 'dynStyles' is used to +-- set the CSS color for an element, essentially no other CSS property +-- can be applied to this element, as they will be overwritten by +-- 'dynStyles'. +dynStyles :: DynVal Text -> HtmlM () +dynStyles = dynProp "style" +{-# INLINE dynStyles #-} + +dynValue :: DynVal Text -> HtmlM () +dynValue = dynProp "value" +{-# INLINE dynValue #-} + +dynClass :: DynVal Text -> HtmlM () +dynClass = dynProp "className" +{-# INLINE dynClass #-} + +dynChecked :: DynVal Bool -> HtmlM () +dynChecked = dynBoolProp "checked" +{-# INLINE dynChecked #-} + +dynDisabled :: DynVal Bool -> HtmlM () +dynDisabled = dynBoolProp "disabled" +{-# INLINE dynDisabled #-} + +title_ :: Text -> HtmlM () +title_ = property "title" +{-# INLINE title_ #-} + +selected_ :: Bool -> HtmlM () +selected_ = boolProperty "selected" +{-# INLINE selected_ #-} + +hidden_ :: Bool -> HtmlM () +hidden_ = boolProperty "hidden" +{-# INLINE hidden_ #-} + +value_ :: Text -> HtmlM () +value_ = property "value" +{-# INLINE value_ #-} + +defaultValue_ :: Text -> HtmlM () +defaultValue_ = property "defaultValue" +{-# INLINE defaultValue_ #-} + +accept_ :: Text -> HtmlM () +accept_ = property "accept" +{-# INLINE accept_ #-} + +acceptCharset_ :: Text -> HtmlM () +acceptCharset_ = property "acceptCharset" +{-# INLINE acceptCharset_ #-} + +action_ :: Text -> HtmlM () +action_ = property "action" +{-# INLINE action_ #-} + +autocomplete_ :: Bool -> HtmlM () +autocomplete_ b = property "autocomplete" (if b then "on" else "off") +{-# INLINE autocomplete_ #-} + +autosave_ :: Text -> HtmlM () +autosave_ = property "autosave" +{-# INLINE autosave_ #-} + +disabled_ :: Bool -> HtmlM () +disabled_ = boolProperty "disabled" +{-# INLINE disabled_ #-} + +enctype_ :: Text -> HtmlM () +enctype_ = property "enctype" +{-# INLINE enctype_ #-} + +formation_ :: Text -> HtmlM () +formation_ = property "formation" +{-# INLINE formation_ #-} + +list_ :: Text -> HtmlM () +list_ = property "list" +{-# INLINE list_ #-} + +maxlength_ :: Text -> HtmlM () +maxlength_ = property "maxlength" +{-# INLINE maxlength_ #-} + +minlength_ :: Text -> HtmlM () +minlength_ = property "minlength" +{-# INLINE minlength_ #-} + +method_ :: Text -> HtmlM () +method_ = property "method" +{-# INLINE method_ #-} + +multiple_ :: Bool -> HtmlM () +multiple_ = boolProperty "multiple" +{-# INLINE multiple_ #-} + +novalidate_ :: Bool -> HtmlM () +novalidate_ = boolProperty "noValidate" +{-# INLINE novalidate_ #-} + +pattern_ :: Text -> HtmlM () +pattern_ = property "pattern" +{-# INLINE pattern_ #-} + +readonly_ :: Bool -> HtmlM () +readonly_ = boolProperty "readOnly" +{-# INLINE readonly_ #-} + +required_ :: Bool -> HtmlM () +required_ = boolProperty "required" +{-# INLINE required_ #-} + +size_ :: Text -> HtmlM () +size_ = property "size" +{-# INLINE size_ #-} + +forProp_ :: Text -> HtmlM () +forProp_ = property "for" +{-# INLINE forProp_ #-} + +ref_ :: Text -> HtmlM () +ref_ = property "ref" +{-# INLINE ref_ #-} + +formProp_ :: Text -> HtmlM () +formProp_ = property "form" +{-# INLINE formProp_ #-} + +max_ :: Text -> HtmlM () +max_ = property "max" +{-# INLINE max_ #-} + +min_ :: Text -> HtmlM () +min_ = property "min" +{-# INLINE min_ #-} + +step_ :: Text -> HtmlM () +step_ = property "step" +{-# INLINE step_ #-} + +cols_ :: Text -> HtmlM () +cols_ = property "cols" +{-# INLINE cols_ #-} + +rows_ :: Text -> HtmlM () +rows_ = property "rows" +{-# INLINE rows_ #-} + +wrap_ :: Text -> HtmlM () +wrap_ = property "wrap" +{-# INLINE wrap_ #-} + +target_ :: Text -> HtmlM () +target_ = property "target" +{-# INLINE target_ #-} + +download_ :: Text -> HtmlM () +download_ = property "download" +{-# INLINE download_ #-} + +downloadAs_ :: Text -> HtmlM () +downloadAs_ = property "downloadAs" +{-# INLINE downloadAs_ #-} + +hreflang_ :: Text -> HtmlM () +hreflang_ = property "hreflang" +{-# INLINE hreflang_ #-} + +media_ :: Text -> HtmlM () +media_ = property "media" +{-# INLINE media_ #-} + +ping_ :: Text -> HtmlM () +ping_ = property "ping" +{-# INLINE ping_ #-} + +rel_ :: Text -> HtmlM () +rel_ = property "rel" +{-# INLINE rel_ #-} + +ismap_ :: Text -> HtmlM () +ismap_ = property "ismap" +{-# INLINE ismap_ #-} + +usemap_ :: Text -> HtmlM () +usemap_ = property "usemap" +{-# INLINE usemap_ #-} + +shape_ :: Text -> HtmlM () +shape_ = property "shape" +{-# INLINE shape_ #-} + +coords_ :: Text -> HtmlM () +coords_ = property "coords" +{-# INLINE coords_ #-} + +src_ :: Text -> HtmlM () +src_ = property "src" +{-# INLINE src_ #-} + +height_ :: Text -> HtmlM () +height_ = property "height" +{-# INLINE height_ #-} + +width_ :: Text -> HtmlM () +width_ = property "width" +{-# INLINE width_ #-} + +alt_ :: Text -> HtmlM () +alt_ = property "alt" +{-# INLINE alt_ #-} + +autoplay_ :: Bool -> HtmlM () +autoplay_ = boolProperty "autoplay" +{-# INLINE autoplay_ #-} + +controls_ :: Bool -> HtmlM () +controls_ = boolProperty "controls" +{-# INLINE controls_ #-} + +loop_ :: Bool -> HtmlM () +loop_ = boolProperty "loop" +{-# INLINE loop_ #-} + +preload_ :: Text -> HtmlM () +preload_ = property "preload" +{-# INLINE preload_ #-} + +poster_ :: Text -> HtmlM () +poster_ = property "poster" +{-# INLINE poster_ #-} + +default_ :: Bool -> HtmlM () +default_ = boolProperty "default" +{-# INLINE default_ #-} + +kind_ :: Text -> HtmlM () +kind_ = property "kind" +{-# INLINE kind_ #-} + +srclang_ :: Text -> HtmlM () +srclang_ = property "srclang" +{-# INLINE srclang_ #-} + +sandbox_ :: Text -> HtmlM () +sandbox_ = property "sandbox" +{-# INLINE sandbox_ #-} + +seamless_ :: Text -> HtmlM () +seamless_ = property "seamless" +{-# INLINE seamless_ #-} + +srcdoc_ :: Text -> HtmlM () +srcdoc_ = property "srcdoc" +{-# INLINE srcdoc_ #-} + +reversed_ :: Text -> HtmlM () +reversed_ = property "reversed" +{-# INLINE reversed_ #-} + +start_ :: Text -> HtmlM () +start_ = property "start" +{-# INLINE start_ #-} + +align_ :: Text -> HtmlM () +align_ = property "align" +{-# INLINE align_ #-} + +colspan_ :: Text -> HtmlM () +colspan_ = attribute "colspan" +{-# INLINE colspan_ #-} + +rowspan_ :: Text -> HtmlM () +rowspan_ = attribute "rowspan" +{-# INLINE rowspan_ #-} + +headers_ :: Text -> HtmlM () +headers_ = property "headers" +{-# INLINE headers_ #-} + +scope_ :: Text -> HtmlM () +scope_ = property "scope" +{-# INLINE scope_ #-} + +async_ :: Text -> HtmlM () +async_ = property "async" +{-# INLINE async_ #-} + +charset_ :: Text -> HtmlM () +charset_ = property "charset" +{-# INLINE charset_ #-} + +content_ :: Text -> HtmlM () +content_ = property "content" +{-# INLINE content_ #-} + +defer_ :: Text -> HtmlM () +defer_ = property "defer" +{-# INLINE defer_ #-} + +httpEquiv_ :: Text -> HtmlM () +httpEquiv_ = property "httpEquiv" +{-# INLINE httpEquiv_ #-} + +language_ :: Text -> HtmlM () +language_ = property "language" +{-# INLINE language_ #-} + +scoped_ :: Text -> HtmlM () +scoped_ = property "scoped" +{-# INLINE scoped_ #-} + +type_ :: Text -> HtmlM () +type_ = property "type" +{-# INLINE type_ #-} + +name_ :: Text -> HtmlM () +name_ = property "name" +{-# INLINE name_ #-} + +href_ :: Text -> HtmlM () +href_ = property "href" +{-# INLINE href_ #-} + +id_ :: Text -> HtmlM () +id_ = property "id" +{-# INLINE id_ #-} + +placeholder_ :: Text -> HtmlM () +placeholder_ = property "placeholder" +{-# INLINE placeholder_ #-} + +checked_ :: Bool -> HtmlM () +checked_ = boolProperty "checked" +{-# INLINE checked_ #-} + +autofocus_ :: Bool -> HtmlM () +autofocus_ = boolProperty "autofocus" +{-# INLINE autofocus_ #-} + +class_ :: Text -> HtmlM () +class_ = property "className" +{-# INLINE class_ #-} + +data_ :: Text -> Text -> HtmlM () +data_ k v = property ("data-" <> k) v +{-# INLINE data_ #-} + +role_ :: Text -> HtmlM () +role_ = attribute "role" +{-# INLINE role_ #-} + +style_ :: Text -> HtmlM () +style_ = property "style" +{-# INLINE style_ #-} diff --git a/src/Clickable/Types.hs b/src/Clickable/Types.hs new file mode 100644 index 0000000..91897a5 --- /dev/null +++ b/src/Clickable/Types.hs @@ -0,0 +1,64 @@ +module Clickable.Types where + +import Control.Monad +import Control.Monad.Reader +import Control.Monad.State +import Data.IORef +import Data.List qualified as List +import Data.Text (Text) +import Data.Tuple +import Data.Map (Map) +import Data.Map qualified as Map +import GHC.Generics +import GHC.Exts hiding (build) +import Unsafe.Coerce + +import Clickable.FFI +import Wasm.Compat.Prim + +data DynVar a where + DynVar :: SourceId -> IORef a -> DynVar a + +data DynVal a where + ConstVal :: a -> DynVal a + FromVar :: DynVar a -> DynVal a + MapVal :: DynVal a -> (a -> b) -> DynVal b + SplatVal :: DynVal (a -> b) -> DynVal a -> DynVal b + +instance Functor DynVal where fmap = flip MapVal + +instance Applicative DynVal where pure = ConstVal; (<*>) = SplatVal + +fromVar :: DynVar a -> DynVal a +fromVar = FromVar + +newtype ResourceScope = ResourceScope {unResourceScope :: Int} + deriving newtype (Eq, Ord, Show) + +newtype SourceId = SourceId {unSourceId :: Int} + deriving newtype (Eq, Ord, Show) + +newtype HtmlM a = HtmlM { unHtmlM :: ReaderT JSVal ClickM a } + deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader JSVal) + +newtype ClickM a = ClickM {unClickM :: ReaderT InternalEnv IO a } + deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader InternalEnv) + +instance MonadState InternalState ClickM where + state f = ClickM $ ReaderT $ \e -> atomicModifyIORef' e.internal_state_ref $ swap . f + +data InternalEnv = InternalEnv + { scope :: ResourceScope + , internal_state_ref :: IORef InternalState + } deriving (Generic) + +data InternalState = InternalState + { subscriptions :: [(ResourceScope, SourceId, Any -> ClickM ())] + , finalizers :: [(ResourceScope, FinalizerVal)] + , transaction_queue :: Map SourceId (ClickM ()) + , next_id :: Int + } deriving (Generic) + +data FinalizerVal + = CustomFinalizer (ClickM ()) + | ScopeFinalizer ResourceScope diff --git a/src/HtmlT.hs b/src/HtmlT.hs deleted file mode 100644 index ebfb2c3..0000000 --- a/src/HtmlT.hs +++ /dev/null @@ -1,15 +0,0 @@ - -module HtmlT (module X) where - -import HtmlT.Base as X -import HtmlT.DOM as X -import HtmlT.Element as X -import HtmlT.Main as X -import HtmlT.Property as X -import HtmlT.Types as X - -import HtmlT.Event as X hiding - ( unsafeSubscribe - , unsafeTrigger - , Lens' - ) diff --git a/src/HtmlT/Base.hs b/src/HtmlT/Base.hs deleted file mode 100644 index 55ad268..0000000 --- a/src/HtmlT/Base.hs +++ /dev/null @@ -1,364 +0,0 @@ -{-| -Most essential public definions --} -module HtmlT.Base where - -import Control.Monad -import Control.Monad.Reader -import Control.Monad.Trans.Maybe -import Data.Foldable -import Data.IORef -import Data.Map qualified as Map - -import HtmlT.DOM -import HtmlT.Event -import HtmlT.Internal -import HtmlT.Types -import JavaScript.Compat.Marshal -import JavaScript.Compat.Prim -import JavaScript.Compat.String (JSString(..)) - --- | Create a DOM element with a given tag name and attach it to --- 'html_current_element'. Attributes, properties and children nodes can --- be added from inside the second argument --- --- > el "div" do --- > prop "className" "container" --- > el "span" $ text "Lorem Ipsum" -el :: JSString -> Html a -> Html a -el tag child = do - newRootEl <- liftIO (createElement tag) - appendHtmlT newRootEl child - --- | Same as 'el' but allows to specify element's namespace --- https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS --- --- > elns "http://www.w3.org/2000/svg" "svg" do --- > prop "height" "210" --- > prop "width" "400" --- > elns "http://www.w3.org/2000/svg" "path" do --- > prop "d" "M150 0 L75 200 L225 200 Z" -elns :: JSString -> JSString -> Html a -> Html a -elns ns tag child = do - newRootEl <- liftIO (createElementNS ns tag) - appendHtmlT newRootEl child - --- | Create a TextNode and attach it to 'html_current_element' -text :: JSString -> Html () -text txt = do - textNode <- liftIO (createTextNode txt) - insertNode textNode - --- | Create a TextNode with dynamic content -dynText :: Dynamic JSString -> Html () -dynText d = do - txt <- readDyn d - textNode <- liftIO (createTextNode txt) - void $ subscribe (updates d) \new -> void $ liftIO do - setTextValue textNode new - insertNode textNode - --- | Assign a property to 'html_current_element'. Don't confuse --- attributes and properties --- https://stackoverflow.com/questions/6003819/what-is-the-difference-between-properties-and-attributes-in-html -prop :: ToJSVal v => JSString -> v -> Html () -prop key val = do - rootEl <- asks html_current_element - v <- liftIO $ toJSVal val - liftIO $ js_setProp (unDOMElement rootEl) key v - --- | Assign a property with dynamic content to the root element -dynProp - :: (ToJSVal v, FromJSVal v) - => JSString - -> Dynamic v - -> Html () -dynProp jsKey dyn = do - el <- asks html_current_element - performDyn $ liftIO . setup el <$> dyn - where - setup el t = toJSVal t - >>= js_setProp (unDOMElement el) jsKey - --- | Assign an attribute to the root element. Don't confuse attributes --- and properties --- https://stackoverflow.com/questions/6003819/what-is-the-difference-between-properties-and-attributes-in-html -attr :: JSString -> JSString -> Html () -attr k v = do - el <- asks html_current_element - liftIO $ setAttribute el k v - --- | Assign an attribute with dynamic content to the root element -dynAttr :: JSString -> Dynamic JSString -> Html () -dynAttr k d = do - el <- asks html_current_element - performDyn $ liftIO . setAttribute el k <$> d - --- | Attach listener to the root element. First agument is the name --- of the DOM event to listen. Second is the callback that accepts the fired --- DOM event object --- --- > el "button" do --- > on "click" \_event -> do --- > liftIO $ putStrLn "Clicked!" --- > text "Click here" -on :: EventName -> (DOMEvent -> Step ()) -> Html () -on name k = do - el <- asks html_current_element - onGlobalEvent defaultListenerOpts (nodeFromElement el) name k - --- | Same as 'on' but ignores 'DOMEvent' inside the callback -on_ :: EventName -> Step () -> Html () -on_ name = on name . const - --- | Same as 'on' but allows to specify 'ListenerOpts' -onOptions :: EventName -> ListenerOpts -> (DOMEvent -> Step ()) -> Html () -onOptions name opts k = do - el <- asks html_current_element - onGlobalEvent opts (nodeFromElement el) name k - -decodeEvent :: (JSVal -> MaybeT Step a) -> (a -> Step ()) -> DOMEvent -> Step () -decodeEvent dec act (DOMEvent jsevent) = - runMaybeT (dec jsevent) >>= maybe (pure ()) act - --- | Attach a listener to arbitrary target, not just the current root --- element (usually that would be @window@, @document@ or @body@ --- objects) -onGlobalEvent - :: MonadReactive m - => ListenerOpts - -- ^ Specify whether to call @event.stopPropagation()@ and - -- @event.preventDefault()@ on the fired event - -> DOMNode - -- ^ Event target - -> EventName - -- ^ Event name - -> (DOMEvent -> Step ()) - -- ^ Callback that accepts reference to the DOM event - -> m () -onGlobalEvent opts target name f = do - ReactiveEnv{renv_finalizers} <- askReactiveEnv - let - event = Event \re cb -> liftIO do - finalizerId <- nextQueueId re - unlisten <- addEventListener opts target name $ - dynStep . cb . f - modifyIORef' renv_finalizers $ Map.insert - (FinalizerQueueId finalizerId) (CustomFinalizer unlisten) - subscribe event id - --- | Assign CSS classes to the current root element. Compared to @prop --- "className"@ can be used multiple times for the same root --- --- > el "div" do --- > classes "container row" --- > classes "mt-1 mb-2" --- classes :: JSString -> Html () --- classes cs = do --- rootEl <- asks html_current_element --- for_ (T.splitOn " " cs) $ liftIO . classListAdd rootEl - --- | Assign a single CSS class dynamically based on the value held by --- the given Dynamic --- --- > showRef <- newRef False --- > el "div" do --- > toggleClass "show" $ fromRef showRef --- > el "button" do --- > on_ "click" $ modifyRef showRef not --- > text "Toggle visibility" -toggleClass :: JSString -> Dynamic Bool -> Html () -toggleClass cs dyn = do - rootEl <- asks html_current_element - performDyn $ liftIO . setup rootEl cs <$> dyn - where - setup rootEl cs = \case - True -> classListAdd rootEl cs - False -> classListRemove rootEl cs - --- | Assign a boolean attribute dynamically based on the value held by --- the given Dynamic --- --- > hiddenRef <- newRef True --- > el "div" do --- > toggleAttr "hidden" $ fromRef hiddenRef --- > el "button" do --- > on_ "click" $ modifyRef hiddenRef not --- > text "Toggle visibility" -toggleAttr :: JSString -> Dynamic Bool -> Html () -toggleAttr att dyn = do - rootEl <- asks html_current_element - performDyn $ liftIO . setup rootEl att <$> dyn - where - setup rootEl name = \case - True -> setAttribute rootEl name "on" - False -> removeAttribute rootEl name - --- | Assign a CSS property to the root dynamically based on the value --- held by the given Dynamic --- --- > colorRef <- newRef True --- > el "button" do --- > dynStyle "background" $ bool "initial" "red" <$> fromRef colorRef --- > on_ "click" $ modifyRef colorRef not --- > text "Toggle background color" -dynStyle :: JSString -> Dynamic JSString -> Html () -dynStyle cssProp dyn = do - rootEl <- asks html_current_element - performDyn $ liftIO . setup rootEl <$> dyn - where - setup el t = do - styleVal <- getProp (unDOMElement el) "style" - cssVal <- toJSVal t - js_setProp styleVal cssProp cssVal - --- | Alias for @pure ()@, useful when some Html action is expected. -blank :: Applicative m => m () -blank = pure () - --- | Attach a dynamic list to the root. Convenient for displaying --- small dynamic collections (<100 elements). --- --- > listRef <- newRef ["One", "Two", "Three"] --- > el "ul" do --- > simpleList listRef \_idx elemRef -> do --- > el "li" $ dynText $ fromRef elemRef --- > el "button" do --- > on_ "click" $ modifyRef listRef ("New Item":) --- > text "Append new item" -simpleList - :: forall a. Dynamic [a] - -- ^ Some dynamic data from the above scope - -> (Int -> DynRef a -> Html ()) - -- ^ Function to build children widget. Accepts the index inside the - -- collection and dynamic data for that particular element - -> Html () -simpleList listDyn h = do - boundary <- insertBoundary - htmlEnv <- asks \h -> h {html_content_boundary = Just boundary} - prevValue <- liftIO $ newIORef [] - elemEnvsRef <- liftIO $ newIORef ([] :: [ElemEnv a]) - let - setup :: Int -> [a] -> [ElemEnv a] -> Step [ElemEnv a] - setup idx new existing = case (existing, new) of - ([], []) -> return [] - -- New list is longer, append new elements - ([], x:xs) -> do - finalizers <- liftIO $ newIORef Map.empty - elementRef <- liftIO $ execReactiveT (html_reactive_env htmlEnv) $ newRef x - boundary <- liftIO $ execHtmlT htmlEnv insertBoundary - let - elementEnv = htmlEnv - { html_reactive_env = (html_reactive_env htmlEnv) - { renv_finalizers = finalizers } - , html_content_boundary = Just boundary - } - liftIO $ execHtmlT elementEnv $ h idx elementRef - let newElem = ElemEnv elementEnv elementRef - fmap (newElem:) $ setup (idx + 1) xs [] - -- New list is shorter, delete the elements that no longer - -- present in the new list - (r:rs, []) -> do - liftIO $ finalizeElems True (r:rs) - return [] - -- Update child elements along the way - (r:rs, y:ys) -> do - writeRef (ee_dyn_ref r) y - fmap (r:) $ setup (idx + 1) ys rs - finalizeElems remove = traverse_ \ElemEnv{ee_html_env} -> do - when remove $ - mapM_ removeBoundary $ html_content_boundary ee_html_env - let re = html_reactive_env ee_html_env - finalizers <- readIORef $ renv_finalizers re - applyFinalizer re finalizers - updateList new = do - old <- liftIO $ atomicModifyIORef' prevValue (new,) - eenvs <- liftIO $ readIORef elemEnvsRef - newEenvs <- setup 0 new eenvs - liftIO $ writeIORef elemEnvsRef newEenvs - performDyn $ updateList <$> listDyn - void $ installFinalizer $ readIORef elemEnvsRef >>= finalizeElems False - --- | First build a DOM with the widget that is currently held by the --- given Dynamic, then rebuild it every time Dynamic's value --- changes. Useful for SPA routing, tabbed components etc. --- --- > routeRef <- newRef Home --- > el "div" do --- > dyn $ routeRef <&> \case --- > Home -> homeWidget --- > Blog -> blogWidget --- > Resume -> resumeWidget --- > el "button" do --- > on_ "click" $ writeRef routeRef Blog --- > text "Show my blog page" -dyn :: Dynamic (Html ()) -> Html () -dyn d = do - htmlEnv <- ask - childRef <- liftIO (newIORef Nothing) - boundary <- insertBoundary - let - finalizeEnv newEnv = do - readIORef childRef >>= \case - Just HtmlEnv{html_reactive_env} -> do - finalizers <- atomicModifyIORef' (renv_finalizers html_reactive_env) (Map.empty,) - applyFinalizer html_reactive_env finalizers - Nothing -> return () - writeIORef childRef newEnv - setup html = liftIO do - finalizers <- newIORef Map.empty - let - newEnv = htmlEnv - { html_reactive_env = (html_reactive_env htmlEnv) - { renv_finalizers = finalizers } - , html_content_boundary = Just boundary - } - finalizeEnv (Just newEnv) - clearBoundary boundary - execHtmlT newEnv html - installFinalizer (finalizeEnv Nothing) - performDyn $ setup <$> d - --- | Run an action before the current node is detached from the DOM -installFinalizer :: MonadReactive m => IO () -> m FinalizerKey -installFinalizer fin = do - renv <- askReactiveEnv - finalizerId <- liftIO $ nextQueueId renv - let finalizerKey = FinalizerQueueId finalizerId - liftIO $ modifyIORef renv.renv_finalizers $ - Map.insert finalizerKey $ CustomFinalizer fin - return finalizerKey - --- | Attach resulting DOM to the given node instead of --- 'html_current_element'. Might be useful for implementing modal --- dialogs, tooltips etc. Similar to what called portals in React --- ecosystem -portal :: MonadIO m => DOMElement -> HtmlT m a -> HtmlT m a -portal newRootEl html = do - boundary <- local (\e -> e - { html_current_element = newRootEl - , html_content_boundary = Nothing - }) insertBoundary - result <- local (\e -> e - { html_current_element = newRootEl - , html_content_boundary = Just boundary - }) html - installFinalizer $ removeBoundary boundary - return result - --- | Parse given text as HTML and attach the resulting tree to --- 'html_current_element'. This way you can create not only HTML but --- anything that @innerHTML@ property can create (e.g. SVG) --- --- > -- Create a div with an SVG image inside that shows a black --- > -- circle --- > div_ [] do --- > unsafeHtml "\ --- > \\ --- > \" -unsafeHtml :: MonadIO m => JSString -> HtmlT m () -unsafeHtml htmlText = do - henv <- ask - let anchor = fmap boundary_end henv.html_content_boundary - liftIO $ unsafeInsertHtml henv.html_current_element anchor - htmlText diff --git a/src/HtmlT/DOM.hs b/src/HtmlT/DOM.hs deleted file mode 100644 index e48af94..0000000 --- a/src/HtmlT/DOM.hs +++ /dev/null @@ -1,433 +0,0 @@ -{-| -Functions and definitions to manipulate and query the DOM tree --} -{-# OPTIONS_GHC -fno-warn-orphans #-} -{-# LANGUAGE CPP #-} -{-# LANGUAGE ForeignFunctionInterface #-} -{-# LANGUAGE JavaScriptFFI #-} -module HtmlT.DOM where - -import Control.Monad -import Control.Monad.Reader -import Control.Monad.Trans.Maybe -import Data.Coerce -import GHC.Exts as Exts -import GHC.Generics -import Unsafe.Coerce - -import HtmlT.Types -import JavaScript.Compat.Foreign.Callback -import JavaScript.Compat.Marshal -import JavaScript.Compat.Prim -import JavaScript.Compat.String (JSString) -import JavaScript.Compat.String qualified as JSS - -data ListenerOpts = ListenerOpts - { lo_stop_propagation :: Bool - -- ^ If true call @event.stopPropagation()@ - , lo_prevent_default :: Bool - -- ^ If true call @event.preventDefault()@ - , lo_sync_callback :: Bool - -- ^ If true create callback with @syncCallback1 ThrowWouldBlock@ - -- otherwise — @asyncCallback1@ this is relevant for example when - -- listening to @BeforeUnloadEvent@ - -- https://developer.mozilla.org/en-US/docs/Web/API/BeforeUnloadEvent - } deriving stock (Generic) - -defaultListenerOpts :: ListenerOpts -defaultListenerOpts = ListenerOpts False False False - --- | Get global Window object @window@ --- https://developer.mozilla.org/en-US/docs/Web/API/Window -getCurrentWindow :: MonadIO m => m JSVal -getCurrentWindow = liftIO js_getCurrentWindow - --- | Get global Document object --- https://developer.mozilla.org/en-US/docs/Web/API/Document -getCurrentDocument :: MonadIO m => m JSVal -getCurrentDocument = liftIO js_getCurrentDocument - --- | Get Document.body property --- https://developer.mozilla.org/en-US/docs/Web/API/Document/body -getCurrentBody :: MonadIO m => m DOMElement -getCurrentBody = liftIO $ fmap DOMElement js_getCurrentBody - --- | DOMElement.appendChild() --- https://developer.mozilla.org/en-US/docs/Web/API/DOMNode/appendChild -appendChild :: DOMElement -> DOMNode -> IO () -appendChild = js_appendChild - --- | Element.setAttribute() --- https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute -setAttribute :: DOMElement -> JSString -> JSString -> IO () -setAttribute e k v = js_setAttribute e k v - --- | Element.removeAttribute() --- https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute -removeAttribute :: DOMElement -> JSString -> IO () -removeAttribute e k = js_removeAttribute e k - --- | DOMNode.removeChild() --- https://developer.mozilla.org/en-US/docs/Web/API/DOMNode/removeChild -removeChild :: DOMElement -> DOMNode -> IO () -removeChild = js_removeChild - --- | Document.createElement() --- https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement -createElement :: JSString -> IO DOMElement -createElement = js_createElement - --- | Document.createElementNS() --- https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS -createElementNS :: JSString -> JSString -> IO DOMElement -createElementNS n t = js_createElementNS n t - --- | Document.createTextNode() --- https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode -createTextNode :: JSString -> IO DOMNode -createTextNode = js_createTextNode - --- | Document.createComment() --- https://developer.mozilla.org/en-US/docs/Web/API/Document/createComment -createComment :: JSString -> IO DOMNode -createComment = js_createComment - --- | Element.classList.add() --- https://developer.mozilla.org/en-US/docs/Web/API/Element/classList -classListAdd :: DOMElement -> JSString -> IO () -classListAdd e c = js_classListAdd e c - --- | Element.classList.remove() --- https://developer.mozilla.org/en-US/docs/Web/API/Element/classList -classListRemove :: DOMElement -> JSString -> IO () -classListRemove e c = js_classListRemove e c - --- | Assign text to DOMNode.nodeValue --- https://developer.mozilla.org/en-US/docs/Web/API/DOMNode/nodeValue -setTextValue :: DOMNode -> JSString -> IO () -setTextValue v = js_setTextValue v - --- | Insert raw HTML code, similar to @parent.innerHTML = rawHtml@ but --- does not removes siblings -unsafeInsertHtml :: DOMElement -> Maybe DOMNode -> JSString -> IO () -unsafeInsertHtml parent manchor rawHtml = js_unsafeInsertHtml parent - (maybeToNullable manchor) rawHtml - --- | Assuming given 'ContentBoundary' was inserted into the @parent@ --- element remove all the content inside the boundary. -clearBoundary :: ContentBoundary -> IO () -clearBoundary ContentBoundary{..} = - js_clearBoundary boundary_begin boundary_end - --- | Detach 'ContentBoundary' from the DOM and everything inside the --- boundary. -removeBoundary :: ContentBoundary -> IO () -removeBoundary ContentBoundary{..} = do - js_clearBoundary boundary_begin boundary_end - js_detachBoundary boundary_begin boundary_end - --- | Run a given callback on BeforeUnloadEvent --- https://developer.mozilla.org/en-US/docs/Web/API/BeforeUnloadEvent -onBeforeUnload :: IO () -> IO () -onBeforeUnload cb = do - syncCb <- syncCallback ThrowWouldBlock cb - js_onBeforeUnload syncCb - --- | EventTarget.addEventListener() --- https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener -addEventListener - :: ListenerOpts - -> DOMNode - -> EventName - -> (DOMEvent -> IO ()) - -> IO (IO ()) -addEventListener ListenerOpts{..} target name f = do - hscb <- mkcallback (f . DOMEvent) - jscb <- withopts hscb - js_callMethod2 (coerce target) "addEventListener" - (JSS.toJSValPure (unEventName name)) (unsafeCoerce jscb) - return do - js_callMethod2 (coerce target) "removeEventListener" - (JSS.toJSValPure (unEventName name)) (unsafeCoerce jscb) - releaseCallback hscb - where - mkcallback = if lo_sync_callback - then syncCallback1 ThrowWouldBlock - else asyncCallback1 - withopts = js_callbackWithOptions lo_stop_propagation lo_prevent_default - --- | Collection of deltaX, deltaY and deltaZ properties from WheelEvent --- https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent -data MouseDelta = MouseDelta - { md_delta_x :: Int - , md_delta_y :: Int - , md_delta_z :: Int - } deriving stock (Eq, Show, Generic) - -mouseDeltaDecoder :: MonadIO m => JSVal -> MaybeT m MouseDelta -mouseDeltaDecoder mouseEvent = do - md_delta_x <- propDecoder "deltaX" mouseEvent - md_delta_y <- propDecoder "deltaY" mouseEvent - md_delta_z <- propDecoder "deltaZ" mouseEvent - return MouseDelta {..} - --- | Pair of two values, might denote either a size or coordinates in --- different contexts -data Point a = Point - { pt_x :: a - , pt_y :: a - } deriving stock (Eq, Show, Ord, Functor, Generic) - --- | Read clientX and clientY properties from MouseEvent --- https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent -clientXYDecoder :: MonadIO m => JSVal -> MaybeT m (Point Int) -clientXYDecoder mouseEvent = do - pt_x <- propDecoder "clientX" mouseEvent - pt_y <- propDecoder "clientY" mouseEvent - return Point {..} - --- | Read offsetX and offsetY properties from MouseEvent --- https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent -offsetXYDecoder :: MonadIO m => JSVal -> MaybeT m (Point Int) -offsetXYDecoder mouseEvent = do - pt_x <- propDecoder "offsetX" mouseEvent - pt_y <- propDecoder "offsetY" mouseEvent - return Point {..} - --- | Read pageX and pageY properties from MouseEvent --- https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent -pageXYDecoder :: MonadIO m => JSVal -> MaybeT m (Point Int) -pageXYDecoder mouseEvent = do - pt_x <- propDecoder "pageX" mouseEvent - pt_y <- propDecoder "pageY" mouseEvent - return Point {..} - --- | Collection of altKey, ctrlKey, metaKey and shiftKey properties --- from KeyboardEvent -data KeyModifiers = KeyModifiers - { kmod_alt_key :: Bool - , kmod_ctrl_key :: Bool - , kmod_meta_key :: Bool - , kmod_shift_key :: Bool - } deriving stock (Eq, Show, Generic) - --- | Read altKey, ctrlKey, metaKey and shiftKey properties from --- KeyboardEvent --- https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent -keyModifiersDecoder :: MonadIO m => JSVal -> MaybeT m KeyModifiers -keyModifiersDecoder keyEvent = do - kmod_alt_key <- propDecoder "altKey" keyEvent - kmod_ctrl_key <- propDecoder "ctrlKey" keyEvent - kmod_meta_key <- propDecoder "metaKey" keyEvent - kmod_shift_key <- propDecoder "shiftKey" keyEvent - return KeyModifiers {..} - --- | Read keyCode properties from KeyboardEvent --- https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode -keyCodeDecoder :: MonadIO m => JSVal -> MaybeT m Int -keyCodeDecoder = propDecoder "keyCode" - --- | Collection of some useful information from KeyboardEvent -data KeyboardEvent = KeyboardEvent - { ke_modifiers :: KeyModifiers - , ke_key :: Maybe JSString - , ke_key_code :: Int - , ke_repeat :: Bool - } deriving stock (Generic) - --- | Read information from KeyboardEvent -keyboardEventDecoder :: MonadIO m => JSVal -> MaybeT m KeyboardEvent -keyboardEventDecoder keyEvent = do - ke_modifiers <- keyModifiersDecoder keyEvent - ke_key <- propDecoder "key" keyEvent - ke_key_code <- propDecoder "keyCode" keyEvent - ke_repeat <- propDecoder "repeat" keyEvent - return KeyboardEvent {..} - --- | Event.target.value --- https://developer.mozilla.org/en-US/docs/Web/API/Event/target --- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-value -valueDecoder :: MonadIO m => JSVal -> MaybeT m JSString -valueDecoder = - propDecoder "target" >=> propDecoder "value" - --- | Event.target.checked --- https://developer.mozilla.org/en-US/docs/Web/API/Event/target --- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#checked -checkedDecoder :: MonadIO m => JSVal -> MaybeT m Bool -checkedDecoder = - propDecoder "target" >=> propDecoder "checked" - -propDecoder :: (MonadIO m, FromJSVal v) => String -> JSVal -> MaybeT m v -propDecoder k obj = do - -- TODO: Make sure it is true that if this guard succeeds, - -- Object.getProp will never throw an exception! - guard $ not (isUndefined obj) && not (isNull obj) - MaybeT $ liftIO $ fromJSVal =<< - getProp obj k - -errorGhcjsOnly :: a -errorGhcjsOnly = error "Only GHCJS is supported" - -#if !defined(javascript_HOST_ARCH) -js_onBeforeUnload :: Callback a -> IO () -js_onBeforeUnload = errorGhcjsOnly - -js_appendChild :: DOMElement -> DOMNode -> IO () = errorGhcjsOnly -js_insertBefore :: DOMElement -> DOMNode -> DOMNode -> IO () = errorGhcjsOnly -js_clearBoundary :: DOMNode -> DOMNode -> IO () = errorGhcjsOnly -js_detachBoundary :: DOMNode -> DOMNode -> IO () = errorGhcjsOnly -js_setAttribute :: DOMElement -> JSString -> JSString -> IO () = errorGhcjsOnly -js_removeAttribute :: DOMElement -> JSString -> IO () = errorGhcjsOnly -js_removeChild :: DOMElement -> DOMNode -> IO () = errorGhcjsOnly -js_replaceChild :: DOMElement -> DOMNode -> DOMNode -> IO () = errorGhcjsOnly -js_createElement :: JSString -> IO DOMElement = errorGhcjsOnly -js_createElementNS :: JSString -> JSString -> IO DOMElement = errorGhcjsOnly -js_createTextNode :: JSString -> IO DOMNode = errorGhcjsOnly -js_createComment :: JSString -> IO DOMNode = errorGhcjsOnly -js_classListAdd :: DOMElement -> JSString -> IO () = errorGhcjsOnly -js_classListRemove :: DOMElement -> JSString -> IO () = errorGhcjsOnly -js_setTextValue :: DOMNode -> JSString -> IO () = errorGhcjsOnly -js_getCurrentWindow :: IO JSVal = errorGhcjsOnly -js_getCurrentDocument :: IO JSVal = errorGhcjsOnly -js_getCurrentBody :: IO JSVal = errorGhcjsOnly -js_unsafeInsertHtml :: DOMElement -> Nullable DOMNode -> JSString -> IO () = errorGhcjsOnly -js_call0 :: JSVal -> IO JSVal = errorGhcjsOnly -js_call1 :: JSVal -> JSVal -> IO JSVal = errorGhcjsOnly -js_call2 :: JSVal -> JSVal -> JSVal -> IO JSVal = errorGhcjsOnly -js_callMethod0 :: JSVal -> JSString -> IO JSVal = errorGhcjsOnly -js_callMethod1 :: JSVal -> JSString -> JSVal -> IO JSVal = errorGhcjsOnly -js_callMethod2 :: JSVal -> JSString -> JSVal -> JSVal -> IO JSVal = errorGhcjsOnly -js_waitDocumentLoad :: IO () = errorGhcjsOnly -js_callbackWithOptions :: Bool -> Bool -> Callback (JSVal -> IO ()) -> IO (Callback (JSVal -> IO ())) = errorGhcjsOnly -js_setProp :: JSVal -> JSString -> JSVal -> IO () = errorGhcjsOnly -#else -foreign import javascript unsafe - "(($1, $2) => $1.appendChild($2))" - js_appendChild :: DOMElement -> DOMNode -> IO () -foreign import javascript unsafe - "(($1, $2, $3) => $1.insertBefore($2, $3))" - js_insertBefore :: DOMElement -> DOMNode -> DOMNode -> IO () -foreign import javascript unsafe - "(($1, $2, $3) => $1.setAttribute($2, $3))" - js_setAttribute :: DOMElement -> JSString -> JSString -> IO () -foreign import javascript unsafe - "(($1, $2) => $1.removeAttribute($2))" - js_removeAttribute :: DOMElement -> JSString -> IO () -foreign import javascript unsafe - "(($1, $2) => $1.removeChild($2))" - js_removeChild :: DOMElement -> DOMNode -> IO () -foreign import javascript unsafe - "(($1, $2, $3) => $1.replaceChild($2, $3))" - js_replaceChild :: DOMElement -> DOMNode -> DOMNode -> IO () -foreign import javascript unsafe - "(($1) => document.createElement($1))" - js_createElement :: JSString -> IO DOMElement -foreign import javascript unsafe - "(($1, $2) => document.createElementNS($1, $2))" - js_createElementNS :: JSString -> JSString -> IO DOMElement -foreign import javascript unsafe - "(($1) => document.createTextNode($1))" - js_createTextNode :: JSString -> IO DOMNode -foreign import javascript unsafe - "(($1) => document.createComment($1))" - js_createComment :: JSString -> IO DOMNode -foreign import javascript unsafe - "(($1, $2) => $1.classList.add($2))" - js_classListAdd :: DOMElement -> JSString -> IO () -foreign import javascript unsafe - "(($1, $2) => $1.classList.remove($2))" - js_classListRemove :: DOMElement -> JSString -> IO () -foreign import javascript unsafe - "(($1, $2) => { $1.nodeValue = $2; })" - js_setTextValue :: DOMNode -> JSString -> IO () -foreign import javascript unsafe - "(($1) => window.addEventListener('beforeunload', $1))" - js_onBeforeUnload :: Callback a -> IO () -foreign import javascript unsafe - "(function(){ return window; })" - js_getCurrentWindow :: IO JSVal -foreign import javascript unsafe - "(function(){ return window.document; })" - js_getCurrentDocument :: IO JSVal -foreign import javascript unsafe - "(function(){ return window.document.body; })" - js_getCurrentBody :: IO JSVal -foreign import javascript unsafe - "(function (begin, end) {\ - for (;;){\ - if (!end.previousSibling\ - || !end.previousSibling.parentNode\ - || end.previousSibling === begin\ - ) break;\ - end.previousSibling.parentNode.removeChild(end.previousSibling);\ - }\ - })" - js_clearBoundary :: DOMNode -> DOMNode -> IO () -foreign import javascript unsafe - "(function (begin, end) {\ - if (begin.parentNode) begin.parentNode.removeChild(begin);\ - if (end.parentNode) end.parentNode.removeChild(end);\ - })" - js_detachBoundary :: DOMNode -> DOMNode -> IO () -foreign import javascript unsafe "(($1) => $1())" - js_call0 :: JSVal -> IO JSVal -foreign import javascript unsafe "(($1, $2) => $1($2))" - js_call1 :: JSVal -> JSVal -> IO JSVal -foreign import javascript unsafe "(($1, $2, $3) => $1($2, $3))" - js_call2 :: JSVal -> JSVal -> JSVal -> IO JSVal -foreign import javascript unsafe "(($1, $2) => $1[$2]())" - js_callMethod0 :: JSVal -> JSString -> IO JSVal -foreign import javascript unsafe "(($1, $2, $3) => $1[$2]($3))" - js_callMethod1 :: JSVal -> JSString -> JSVal -> IO JSVal -foreign import javascript unsafe "(($1, $2, $3, $4) => $1[$2]($3, $4))" - js_callMethod2 :: JSVal -> JSString -> JSVal -> JSVal -> IO JSVal -foreign import javascript unsafe - "(function(el, anchor, htmlString){\ - var div = document.createElement('div');\ - div.innerHTML = htmlString;\ - var tempChilds = [];\ - for (var i = 0; i < div.childNodes.length; i++) {\ - tempChilds.push(div.childNodes[i]);\ - }\ - for (var j = 0; j < tempChilds.length; j++) {\ - div.removeChild(tempChilds[j]);\ - if (anchor) {\ - el.insertBefore(tempChilds[j], anchor);\ - } else{\ - el.appendChild(tempChilds[j]);\ - }\ - }\ - })" - js_unsafeInsertHtml :: DOMElement -> Nullable DOMNode -> JSString -> IO () -foreign import javascript unsafe - "(($1, $2, $3) => function(e) {\ - if ($1) e.stopPropagation();\ - if ($2) e.preventDefault();\ - return $3(e);\ - })" - js_callbackWithOptions :: Bool -> Bool -> Callback (JSVal -> IO ()) -> IO (Callback (JSVal -> IO ())) -foreign import javascript interruptible - "if (document.readyState == 'loading') {\ - addEventListener('DOMContentLoaded', $c);\ - } else {\ - $c();\ - }" - js_waitDocumentLoad :: IO () -foreign import javascript unsafe - "(($1, $2, $3) => { $1[$2] = $3; })" - js_setProp :: JSVal -> JSString -> JSVal -> IO () -foreign import javascript unsafe "(() => null)" - js_null :: JSVal -#endif - -instance (a ~ (), MonadIO m) => IsString (HtmlT m a) where - fromString s = do - HtmlEnv{html_current_element, html_content_boundary} <- ask - let jsstr = toJSString s - textNode <- liftIO $ createTextNode (JSS.fromJSValPure jsstr) - case html_content_boundary of - Just ContentBoundary{boundary_end} -> liftIO $ - js_insertBefore html_current_element textNode boundary_end - Nothing -> liftIO $ appendChild html_current_element textNode - {-# INLINE fromString #-} diff --git a/src/HtmlT/Event.hs b/src/HtmlT/Event.hs deleted file mode 100644 index 2d70fc9..0000000 --- a/src/HtmlT/Event.hs +++ /dev/null @@ -1,466 +0,0 @@ -{-| - -This module offers clear and straightforward implementation of FRP -concepts such as Events and Dynamics, inspired by -Reflex. Additionally, it introduces DynRefs, which are represented by -a Dynamic along with a function to modify the value inside the -Dynamic. DynRef has similar inferface to IORef with functions like -readRef, writeRef, modifyRef etc. --} -module HtmlT.Event where - -import Control.Applicative -import Control.Monad -import Control.Monad.Catch -import Control.Monad.Fix -import Control.Monad.Reader -import Control.Monad.State -import Data.Foldable -import Data.IORef -import Data.Map (Map) -import Data.Map qualified as Map -import Data.Maybe -import Data.Set (Set) -import Data.Set qualified as Set -import Data.Tuple -import GHC.Exts -import GHC.Fingerprint -import GHC.Generics -import Unsafe.Coerce - --- | Represents a stream of event occurrences of type @a@. Its actual --- representation is simply a function that subscribes to the event -newtype Event a = Event - { unEvent :: ReactiveEnv -> Callback a -> IO () - } - --- | Contains a value that is subject to change over time. Provides --- operations for reading the current value ('readDyn') and --- subscribing to its future changes ('updates'). -data Dynamic a = Dynamic - { dynamic_read :: IO a - -- ^ Read current value. Use public alias 'readDyn' instead - , dynamic_updates :: Event a - -- ^ Event that fires when the value changes. Use public alias - -- 'updates' instead - } deriving stock Generic - --- | A mutable variable that allows for subscription to new values. It --- shares a similar API to 'IORef' (see 'readRef', 'writeRef', --- 'modifyRef') -data DynRef a = DynRef - { dynref_dynamic :: Dynamic a - -- ^ Holds the current value and an event that notifies about value - -- modifications - , dynref_modifier :: Modifier a - -- ^ Funtion to update the value - } deriving stock Generic - --- | Function that updates the value inside the 'DynRef' -newtype Modifier a = Modifier - { unModifier :: forall r. Bool -> (a -> (a, r)) -> Step r - -- ^ 'Bool' argument controls whether the modification should - -- trigger an update event. It's possible to update the 'DynRef' - -- without notifying the subscribers for optimization purposes, in - -- cases when you know that all changes already been reflected in - -- the DOM - } - --- | State inside 'Step' -newtype TransactState = TransactState - { unTransactState :: Map QueueId (Step ()) - } deriving newtype (Semigroup, Monoid) - --- | Evaluation of effects triggered by an event firing -newtype Step a = Step { unStep :: StateT TransactState IO a } - deriving newtype - ( Functor, Applicative, Monad, MonadIO, MonadState TransactState, MonadFix - , MonadCatch, MonadThrow, MonadMask - ) - --- | Represents the environment necessary for "reactive" operations, --- such as creating a new 'Event', subscribing to an event etc -data ReactiveEnv = ReactiveEnv - { renv_subscriptions :: IORef (Map QueueId [(QueueId, Callback Any)]) - -- ^ Keeps track of subscriptions - , renv_finalizers :: IORef (Map FinalizerKey FinalizerValue) - -- ^ Keeps track of finalizers. These finalizers will be activated - -- shortly before the current part of the application is terminated. - , renv_id_generator :: IORef QueueId - -- ^ Maintains the next value to be used for generating 'QueueId' - } deriving Generic - --- | Minimal implementation for 'HasReactiveEnv' -newtype ReactiveT m a = ReactiveT - { unReactiveT :: ReaderT ReactiveEnv m a - } deriving newtype - ( Functor, Applicative, Monad, MonadIO, MonadFix, MonadCatch, MonadThrow - , MonadMask - ) - --- | Identifies a computation inside 'TransactState'. The integer --- value within 'QueueId' dictates the execution order in a reactive --- transaction (with higher values executing later). It is also --- utilized to prioritize events derived from other events, ensuring --- they are processed after the source events. This is basically the --- mechanism that prevents double-firing of Dynamics constructed --- using, for instance, the Applicative instance. -newtype QueueId = QueueId {unQueueId :: Int} - deriving newtype (Eq, Show, Ord, Num, Enum, Bounded) - -data FinalizerKey - = FinalizerEventId QueueId - | FinalizerQueueId QueueId - | FinalizerFingerprintId Fingerprint - deriving (Eq, Ord, Generic) - -data FinalizerValue - = SubscriptionSet (Set QueueId) - | CustomFinalizer (IO ()) - deriving Generic - -class HasReactiveEnv m where askReactiveEnv :: m ReactiveEnv - -type MonadReactive m = (HasReactiveEnv m, MonadIO m) - -type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s - -type Callback a = a -> Step () - -type Trigger a = a -> Step () - --- | Create new empty 'ReactiveEnv' -newReactiveEnv :: MonadIO m => m ReactiveEnv -newReactiveEnv = liftIO do - renv_finalizers <- newIORef Map.empty - renv_subscriptions <- newIORef Map.empty - renv_id_generator <- newIORef $ QueueId 0 - return ReactiveEnv {..} - --- | Create new event and a function to supply values to that event --- --- > (event, push) <- newEvent @String --- > push "New Value" -- event fires with given value -newEvent :: forall a m. MonadReactive m => m (Event a, Trigger a) -newEvent = do - renv <- askReactiveEnv - eventId <- liftIO (nextQueueId renv) - let event = Event $ unsafeSubscribe eventId - return (event, unsafeTrigger eventId renv) - --- | Create a new 'DynRef' using given initial value --- --- > showRef <- newRef False --- > dynStep $ writeRef showRef True -- this triggers update event for showRef -newRef :: forall a m. MonadReactive m => a -> m (DynRef a) -newRef initial = do - ref <- liftIO $ newIORef initial - (event, push) <- newEvent - let - modify = Modifier \u f -> do - (new, result) <- liftIO $ atomicModifyIORef' ref \old -> - let (new, result) = f old in - (new, (new, result)) - when u $ push new - return result - dynamic = Dynamic (readIORef ref) event - return $ DynRef dynamic modify - --- | Create a Dynamic that never changes its value -constDyn :: a -> Dynamic a -constDyn a = Dynamic (pure a) never - --- | Event that will never fire -never :: Event a -never = Event \_ _ -> return () - --- | Write new value into a 'DynRef' --- --- > ref <- newRef "Initial value" --- > transactionWrite ref "New value" --- > readRef ref --- "New value" -writeRef :: DynRef a -> a -> Step () -writeRef ref a = modifyRef ref (const a) - --- | Read the current value held by given 'DynRef' --- --- > ref <- newRef "Hello there!" --- > readRef ref --- "Hello there!" -readRef :: MonadIO m => DynRef a -> m a -readRef = readDyn . dynref_dynamic - --- | Update a 'DynRef' by applying given function to the current value --- --- > ref <- newRef [1..3] --- > modifyRef ref $ fmap (*2) --- [2, 4, 6] -modifyRef :: DynRef a -> (a -> a) -> Step () -modifyRef (DynRef _ (Modifier mod)) f = mod True $ (,()) . f - --- | Update a 'DynRef' with first field of the tuple and return back --- the second field. The name is intended to be similar to --- 'atomicModifyIORef' but there are no atomicity guarantees --- whatsoever -atomicModifyRef :: DynRef a -> (a -> (a, r)) -> Step r -atomicModifyRef (DynRef _ (Modifier mod)) f = mod True f - --- | Extract a 'Dynamic' out of 'DynRef' -fromRef :: DynRef a -> Dynamic a -fromRef = dynref_dynamic - --- | Read the value held by a 'Dynamic' -readDyn :: MonadIO m => Dynamic a -> m a -readDyn = liftIO . dynamic_read - --- | Extract the updates Event from a 'Dynamic' -updates :: Dynamic a -> Event a -updates = dynamic_updates - --- | Attach a listener to the event and return an action to detach the --- listener -subscribe :: MonadReactive m => Event a -> Callback a -> m () -subscribe (Event s) k = do - re <- askReactiveEnv - liftIO $ s re k - --- | Executes an action currently held inside the 'Dynamic' and every --- time the value changes. -performDyn :: MonadReactive m => Dynamic (Step ()) -> m () -performDyn d = do - liftIO $ dynamic_read d >>= dynStep - subscribe (dynamic_updates d) id - --- | Apply a lens to the value inside 'DynRef' -lensMap :: forall s a. Lens' s a -> DynRef s -> DynRef a -lensMap l (DynRef sdyn (Modifier smod)) = - DynRef adyn (Modifier amod) - where - adyn = Dynamic - (fmap (getConst . l Const) $ dynamic_read sdyn) - (fmap (getConst . l Const) $ dynamic_updates sdyn) - amod :: forall r. Bool -> (a -> (a, r)) -> Step r - amod u f = smod u $ swap . l (swap . f) - --- | Return a 'Dynamic' for which updates only fire when the value --- actually changes according to Eq instance -holdUniqDyn :: Eq a => Dynamic a -> Dynamic a -holdUniqDyn = holdUniqDynBy (==) -{-# INLINE holdUniqDyn #-} - --- | Same as 'holdUniqDyn' but accepts arbitrary equality test --- function -holdUniqDynBy :: (a -> a -> Bool) -> Dynamic a -> Dynamic a -holdUniqDynBy equalFn Dynamic{..} = Dynamic dynamic_read - (Event \e k -> do - old <- liftIO dynamic_read - oldRef <- liftIO (newIORef old) - unEvent dynamic_updates e \new -> do - old <- liftIO $ atomicModifyIORef' oldRef (new,) - unless (old `equalFn` new) $ k new - ) - --- | Execute the gives finalizers -applyFinalizer :: ReactiveEnv -> Map FinalizerKey FinalizerValue -> IO () -applyFinalizer ReactiveEnv{renv_subscriptions} finalizers = do - forM_ (Map.toList finalizers) \(k, v) -> case (k, v) of - (FinalizerEventId e, SubscriptionSet s) -> - modifyIORef' renv_subscriptions $ - flip Map.alter e $ mfilter (not . Prelude.null) . Just . deleteSubs s . fromMaybe [] - (_, CustomFinalizer io) -> - io - (_, _) -> - return () - where - deleteSubs _ss [] = [] - deleteSubs ss ((s, c):xs) - | Set.member s ss = xs - | otherwise = (s, c) : deleteSubs ss xs - --- | Alternative version if 'fmap' where given function will only be --- called once every time 'Dynamic a' value changes, whereas in 'fmap' --- it would be called once for each subscription per change event. As --- a general guideline, if the function @f! is inexpensive, choose --- @fmap f@. Otherwise, consider using @mapDyn f@. -mapDyn - :: MonadReactive m - => (a -> b) - -> Dynamic a - -> m (Dynamic b) -mapDyn fun adyn = do - initialA <- liftIO $ dynamic_read adyn - latestA <- liftIO $ newIORef initialA - latestB <- liftIO $ newIORef (fun initialA) - renv <- askReactiveEnv - eventId <- liftIO (nextQueueId renv) - let - updates = Event $ unsafeSubscribe eventId - fire = defer eventId do - newB <- liftIO $ fun <$> readIORef latestA - liftIO $ writeIORef latestB newB - unsafeTrigger eventId renv newB - dynamic_updates adyn `subscribe` \newA -> do - liftIO $ writeIORef latestA newA - defer eventId fire - return $ Dynamic (readIORef latestB) updates - --- | Works same way as 'mapDyn' but applies to two dynamics -mapDyn2 - :: MonadReactive m - => (a -> b -> c) - -> Dynamic a - -> Dynamic b - -> m (Dynamic c) -mapDyn2 f adyn bdyn = do - unsafeMapDynN g [unsafeCoerce adyn, unsafeCoerce bdyn] - where - g [a, b] = return $ f (unsafeCoerce a) (unsafeCoerce b) - g _ = error "mapDyn2: impossible happend!" - --- | I hope three arguments will be enough for most cases if more --- needed it's easy to define this function in the application code --- with any required arity -mapDyn3 - :: MonadReactive m - => (a -> b -> c -> d) - -> Dynamic a - -> Dynamic b - -> Dynamic c - -> m (Dynamic d) -mapDyn3 f adyn bdyn cdyn = do - unsafeMapDynN g - [unsafeCoerce adyn, unsafeCoerce bdyn, unsafeCoerce cdyn] - where - g [a, b, c] = return $ f (unsafeCoerce a) (unsafeCoerce b) (unsafeCoerce c) - g _ = error "mapDyn3: impossible happend!" - --- | Takes a list of Dynamics and a function to generate the --- output. The positions of elements in the list of [Any] received by --- the function always correspond to the positions of [Dynamic Any] --- from which these values were generated. The Dynamic created by this --- function will fire at most once per transaction, and only if any of --- the input Dynamics change their values. -unsafeMapDynN - :: MonadReactive m - => ([Any] -> IO a) - -- ^ Construct the output value, from list of input values from - -- corresponding positions of given Dynamics - -> [Dynamic Any] - -- ^ List of input Dynamics - -> m (Dynamic a) -unsafeMapDynN fun dyns = do - renv <- askReactiveEnv - -- TODO: Try if list of IORefs is better than IORef of list - initialInputs <- liftIO $ mapM dynamic_read dyns - initialOutput <- liftIO $ fun initialInputs - latestInputsRef <- liftIO $ newIORef initialInputs - latestOutputRef <- liftIO $ newIORef initialOutput - eventId <- liftIO (nextQueueId renv) - let - fire = defer eventId do - newOutput <- liftIO $ fun =<< readIORef latestInputsRef - liftIO $ writeIORef latestOutputRef newOutput - unsafeTrigger eventId renv newOutput - updates = Event $ unsafeSubscribe eventId - updateList _ _ [] = [] - updateList 0 a (_:xs) = a:xs - updateList n a (x:xs) = x : updateList (pred n) a xs - forM_ (zip [0..] dyns) \(i::Int, adyn) -> do - dynamic_updates adyn `subscribe` \newVal -> do - liftIO $ modifyIORef latestInputsRef $ updateList i newVal - defer eventId fire - return $ Dynamic (readIORef latestOutputRef) updates - --- | Read and increment 'renv_id_generator' -nextQueueId :: ReactiveEnv -> IO QueueId -nextQueueId ReactiveEnv{renv_id_generator} = - atomicModifyIORef' renv_id_generator \eid -> (succ eid, eid) - --- | Defers a computation (typically an event firing) until the end of --- the current reactive transaction. This allows for the avoidance of --- double firing of events constructed from multiple other events. -defer :: QueueId -> Step () -> Step () -defer k act = - Step $ modify \(TransactState s) -> TransactState (Map.insert k act s) - --- | Run a reactive transaction. -dynStep :: MonadIO m => Step a -> m a -dynStep act = liftIO $ loop (TransactState Map.empty) act where - loop :: TransactState -> Step a -> IO a - loop rs (Step act) = do - (r, newRs) <- runStateT act rs - case popQueue newRs of - (Just newAct, newerRs) -> r <$ loop newerRs newAct - (Nothing, _newerRs) -> return r - popQueue intact@(TransactState m) = case Map.minViewWithKey m of - Just ((_, act), rest) -> (Just act, TransactState rest) - Nothing -> (Nothing, intact) - -runReactiveT :: ReactiveT m a -> ReactiveEnv -> m a -runReactiveT r = runReaderT (unReactiveT r) - -execReactiveT :: ReactiveEnv -> ReactiveT m a -> m a -execReactiveT = flip runReactiveT - -unsafeSubscribe :: QueueId -> ReactiveEnv -> Callback a -> IO () -unsafeSubscribe eventId e@ReactiveEnv{renv_subscriptions, renv_finalizers} k = do - subsId <- nextQueueId e - let - newCancel = (subsId, k . unsafeCoerce) - f (SubscriptionSet s1) (SubscriptionSet s2) = SubscriptionSet (s1 <> s2) - -- Unreacheable because FinalizerEventId always should map into - -- SubscriptionSet - f _ s = s - modifyIORef' renv_subscriptions $ - flip Map.alter eventId $ Just . (newCancel :) . fromMaybe [] - modifyIORef' renv_finalizers $ Map.insertWith f (FinalizerEventId eventId) - (SubscriptionSet (Set.singleton subsId)) - -unsafeTrigger :: QueueId -> ReactiveEnv -> a -> Step () -unsafeTrigger eventId ReactiveEnv{..} a = defer eventId do - subscriptions <- liftIO $ readIORef renv_subscriptions - let callbacks = fromMaybe [] $ Map.lookup eventId subscriptions - for_ callbacks $ ($ unsafeCoerce @_ @Any a) . snd - -instance Functor Event where - fmap f (Event s) = Event \e k -> s e . (. f) $ k - --- | Please be aware that in cases where both events fire during the --- same 'Step,' the one having a higher 'EventId' will win, which is --- very hard to predict, use with caution. -instance Semigroup a => Semigroup (Event a) where - (<>) (Event e1) (Event e2) = Event \e k -> mdo - e1 e (defer eventId . k) - e2 e (defer eventId . k) - eventId <- nextQueueId e - return () - -instance Semigroup a => Monoid (Event a) where - mempty = never - -instance Functor Dynamic where - fmap f (Dynamic s u) = Dynamic (fmap f s) (fmap f u) - -instance Applicative Dynamic where - pure = constDyn - (<*>) df da = - let - updatesEvent = Event \e k -> mdo - let - fire newF newA = defer eventId do - f <- liftIO $ maybe (readDyn df) pure newF - a <- liftIO $ maybe (readDyn da) pure newA - k (f a) - unEvent (updates df) e \f -> fire (Just f) Nothing - unEvent (updates da) e \a -> fire Nothing (Just a) - eventId <- nextQueueId e - return () - in - Dynamic - { dynamic_read = liftA2 ($) (dynamic_read df) (dynamic_read da) - , dynamic_updates = updatesEvent - } - -instance Applicative m => HasReactiveEnv (ReactiveT m) where - askReactiveEnv = ReactiveT $ ReaderT pure diff --git a/src/HtmlT/Internal.hs b/src/HtmlT/Internal.hs deleted file mode 100644 index 5bbde7e..0000000 --- a/src/HtmlT/Internal.hs +++ /dev/null @@ -1,43 +0,0 @@ -module HtmlT.Internal where - -import Control.Monad.Reader -import GHC.Generics - -import HtmlT.Event -import HtmlT.Types -import HtmlT.DOM - --- | Auxiliary type helps to implement 'simpleList' -data ElemEnv a = ElemEnv - { ee_html_env :: HtmlEnv - , ee_dyn_ref :: DynRef a - } deriving Generic - --- | Insert given node to @html_current_element@ and run action with --- inserted node as a new root -appendHtmlT :: MonadIO m => DOMElement -> HtmlT m a -> HtmlT m a -appendHtmlT newRootEl html = do - result <- local (\env -> env - { html_current_element = newRootEl - , html_content_boundary = Nothing }) html - result <$ insertNode (nodeFromElement newRootEl) - --- | Insert new node to the end of current boundary -insertNode :: MonadIO m => DOMNode -> HtmlT m () -insertNode n = do - rootEl <- asks html_current_element - boundary <- asks html_content_boundary - case boundary of - Just ContentBoundary{..} -> liftIO $ - js_insertBefore rootEl n boundary_end - Nothing -> liftIO $ appendChild rootEl n - --- | Insert two DOM Comment nodes intended to be used as a boundary for --- dynamic content. -insertBoundary :: MonadIO m => HtmlT m ContentBoundary -insertBoundary = do - boundary_begin <- liftIO $ createComment "ContentBoundary {{" - boundary_end <- liftIO $ createComment "}}" - insertNode boundary_begin - insertNode boundary_end - return ContentBoundary{..} diff --git a/src/HtmlT/Main.hs b/src/HtmlT/Main.hs deleted file mode 100644 index 2a00634..0000000 --- a/src/HtmlT/Main.hs +++ /dev/null @@ -1,78 +0,0 @@ --- | Start and stop browser application -module HtmlT.Main where - -import Control.Monad -import Data.IORef -import GHC.Generics - -import HtmlT.DOM -import HtmlT.Event -import HtmlT.Types - -data StartOpts = StartOpts - { startopts_reactive_env :: ReactiveEnv - -- ^ Typically the program should only have one instance of - -- 'ReactiveEnv', so when there are multiple running 'HtmlT' - -- applications, use this field to share existing 'ReactiveEnv' - -- between all of them - , startopts_root_element :: DOMElement - -- ^ HTMLElement where to attach the elements created by the - -- application - , startopts_wait_document_load :: Bool - -- ^ If True block IO action until main document is fully loaded - , startopts_unload_call_finalizers :: Bool - -- ^ If True run finalizers on @beforeunload@ event. This happens - -- just before browser tab is closed, the code in finalizers should - -- only consist of non-blocking IO - } deriving Generic - --- | Needed to manually finalize and detach the application -data RunningApp = RunningApp - { runapp_html_env :: HtmlEnv - , runapp_boundary :: ContentBoundary - } deriving Generic - --- | Start 'HtmlT' application applying customizations described by --- StartOpts argument -attachOptions :: StartOpts -> Html a -> IO (a, RunningApp) -attachOptions StartOpts{..} render = mdo - -- TODO: doesn't work with javascript-backend - -- when startopts_wait_document_load - -- js_waitDocumentLoad - begin <- createComment "ContentBoundary {{" - end <- createComment "}}" - appendChild startopts_root_element begin - appendChild startopts_root_element end - let - boundary = ContentBoundary begin end - htmlEnv = HtmlEnv - { html_current_element = startopts_root_element - , html_content_boundary = Just boundary - , html_reactive_env = startopts_reactive_env - } - runApp = RunningApp htmlEnv boundary - result <- execHtmlT htmlEnv render - when startopts_unload_call_finalizers $ onBeforeUnload $ do - finalizers <- readIORef $ renv_finalizers startopts_reactive_env - applyFinalizer startopts_reactive_env finalizers - return (result, runApp) - --- | Start the application and attach it to the given HTMLElement -attachTo :: DOMElement -> Html a -> IO (a, RunningApp) -attachTo rootEl html = do - renv <- newReactiveEnv - attachOptions (StartOpts renv rootEl True True) html - --- | Start the application and attach it to current element -attachToBody :: Html a -> IO (a, RunningApp) -attachToBody html = do - bodyEl <- getCurrentBody - attachTo bodyEl html - --- | Run finalizers and detach created elements from the DOM -detach :: RunningApp -> IO () -detach RunningApp{..} = do - finalizers <- readIORef . renv_finalizers . html_reactive_env $ - runapp_html_env - applyFinalizer (html_reactive_env runapp_html_env) finalizers - removeBoundary runapp_boundary diff --git a/src/HtmlT/Property.hs b/src/HtmlT/Property.hs deleted file mode 100644 index 67860e5..0000000 --- a/src/HtmlT/Property.hs +++ /dev/null @@ -1,367 +0,0 @@ -{-| -Shortcuts for common HTML5 attributes and properties --} -module HtmlT.Property where - -import HtmlT.Base -import HtmlT.Event -import HtmlT.Types -import JavaScript.Compat.String (JSString(..)) - - --- TODO: Real-world usage has demonstrated that 'dynStyles' not --- sufficiently composable. For instance, if 'dynStyles' is used to --- set the CSS color for an element, essentially no other CSS property --- can be applied to this element, as they will be overwritten by --- 'dynStyles'. -dynStyles :: Dynamic JSString -> Html () -dynStyles = dynProp "style" -{-# INLINE dynStyles #-} - -dynValue :: Dynamic JSString -> Html () -dynValue = dynProp "value" -{-# INLINE dynValue #-} - -dynClass :: Dynamic JSString -> Html () -dynClass = dynProp "className" -{-# INLINE dynClass #-} - -dynChecked :: Dynamic Bool -> Html () -dynChecked = dynProp "checked" -{-# INLINE dynChecked #-} - -dynDisabled :: Dynamic Bool -> Html () -dynDisabled = dynProp "disabled" -{-# INLINE dynDisabled #-} - -title_ :: JSString -> Html () -title_ = prop "title" -{-# INLINE title_ #-} - -selected_ :: Bool -> Html () -selected_ = prop "selected" -{-# INLINE selected_ #-} - -hidden_ :: Bool -> Html () -hidden_ = prop "hidden" -{-# INLINE hidden_ #-} - -value_ :: JSString -> Html () -value_ = prop "value" -{-# INLINE value_ #-} - -defaultValue_ :: JSString -> Html () -defaultValue_ = prop "defaultValue" -{-# INLINE defaultValue_ #-} - -accept_ :: JSString -> Html () -accept_ = prop "accept" -{-# INLINE accept_ #-} - -acceptCharset_ :: JSString -> Html () -acceptCharset_ = prop "acceptCharset" -{-# INLINE acceptCharset_ #-} - -action_ :: JSString -> Html () -action_ = prop "action" -{-# INLINE action_ #-} - -autocomplete_ :: Bool -> Html () -autocomplete_ b = prop @JSString "autocomplete" (if b then "on" else "off") -{-# INLINE autocomplete_ #-} - -autosave_ :: JSString -> Html () -autosave_ = prop "autosave" -{-# INLINE autosave_ #-} - -disabled_ :: Bool -> Html () -disabled_ = prop "disabled" -{-# INLINE disabled_ #-} - -enctype_ :: JSString -> Html () -enctype_ = prop "enctype" -{-# INLINE enctype_ #-} - -formation_ :: JSString -> Html () -formation_ = prop "formation" -{-# INLINE formation_ #-} - -list_ :: JSString -> Html () -list_ = prop "list" -{-# INLINE list_ #-} - -maxlength_ :: JSString -> Html () -maxlength_ = prop "maxlength" -{-# INLINE maxlength_ #-} - -minlength_ :: JSString -> Html () -minlength_ = prop "minlength" -{-# INLINE minlength_ #-} - -method_ :: JSString -> Html () -method_ = prop "method" -{-# INLINE method_ #-} - -multiple_ :: Bool -> Html () -multiple_ = prop "multiple" -{-# INLINE multiple_ #-} - -novalidate_ :: Bool -> Html () -novalidate_ = prop "noValidate" -{-# INLINE novalidate_ #-} - -pattern_ :: JSString -> Html () -pattern_ = prop "pattern" -{-# INLINE pattern_ #-} - -readonly_ :: Bool -> Html () -readonly_ = prop "readOnly" -{-# INLINE readonly_ #-} - -required_ :: Bool -> Html () -required_ = prop "required" -{-# INLINE required_ #-} - -size_ :: JSString -> Html () -size_ = prop "size" -{-# INLINE size_ #-} - -forProp_ :: JSString -> Html () -forProp_ = prop "for" -{-# INLINE forProp_ #-} - -ref_ :: JSString -> Html () -ref_ = prop "ref" -{-# INLINE ref_ #-} - -formProp_ :: JSString -> Html () -formProp_ = prop "form" -{-# INLINE formProp_ #-} - -max_ :: JSString -> Html () -max_ = prop "max" -{-# INLINE max_ #-} - -min_ :: JSString -> Html () -min_ = prop "min" -{-# INLINE min_ #-} - -step_ :: JSString -> Html () -step_ = prop "step" -{-# INLINE step_ #-} - -cols_ :: JSString -> Html () -cols_ = prop "cols" -{-# INLINE cols_ #-} - -rows_ :: JSString -> Html () -rows_ = prop "rows" -{-# INLINE rows_ #-} - -wrap_ :: JSString -> Html () -wrap_ = prop "wrap" -{-# INLINE wrap_ #-} - -target_ :: JSString -> Html () -target_ = prop "target" -{-# INLINE target_ #-} - -download_ :: JSString -> Html () -download_ = prop "download" -{-# INLINE download_ #-} - -downloadAs_ :: JSString -> Html () -downloadAs_ = prop "downloadAs" -{-# INLINE downloadAs_ #-} - -hreflang_ :: JSString -> Html () -hreflang_ = prop "hreflang" -{-# INLINE hreflang_ #-} - -media_ :: JSString -> Html () -media_ = prop "media" -{-# INLINE media_ #-} - -ping_ :: JSString -> Html () -ping_ = prop "ping" -{-# INLINE ping_ #-} - -rel_ :: JSString -> Html () -rel_ = prop "rel" -{-# INLINE rel_ #-} - -ismap_ :: JSString -> Html () -ismap_ = prop "ismap" -{-# INLINE ismap_ #-} - -usemap_ :: JSString -> Html () -usemap_ = prop "usemap" -{-# INLINE usemap_ #-} - -shape_ :: JSString -> Html () -shape_ = prop "shape" -{-# INLINE shape_ #-} - -coords_ :: JSString -> Html () -coords_ = prop "coords" -{-# INLINE coords_ #-} - -src_ :: JSString -> Html () -src_ = prop "src" -{-# INLINE src_ #-} - -height_ :: JSString -> Html () -height_ = prop "height" -{-# INLINE height_ #-} - -width_ :: JSString -> Html () -width_ = prop "width" -{-# INLINE width_ #-} - -alt_ :: JSString -> Html () -alt_ = prop "alt" -{-# INLINE alt_ #-} - -autoplay_ :: Bool -> Html () -autoplay_ = prop "autoplay" -{-# INLINE autoplay_ #-} - -controls_ :: Bool -> Html () -controls_ = prop "controls" -{-# INLINE controls_ #-} - -loop_ :: Bool -> Html () -loop_ = prop "loop" -{-# INLINE loop_ #-} - -preload_ :: JSString -> Html () -preload_ = prop "preload" -{-# INLINE preload_ #-} - -poster_ :: JSString -> Html () -poster_ = prop "poster" -{-# INLINE poster_ #-} - -default_ :: Bool -> Html () -default_ = prop "default" -{-# INLINE default_ #-} - -kind_ :: JSString -> Html () -kind_ = prop "kind" -{-# INLINE kind_ #-} - -srclang_ :: JSString -> Html () -srclang_ = prop "srclang" -{-# INLINE srclang_ #-} - -sandbox_ :: JSString -> Html () -sandbox_ = prop "sandbox" -{-# INLINE sandbox_ #-} - -seamless_ :: JSString -> Html () -seamless_ = prop "seamless" -{-# INLINE seamless_ #-} - -srcdoc_ :: JSString -> Html () -srcdoc_ = prop "srcdoc" -{-# INLINE srcdoc_ #-} - -reversed_ :: JSString -> Html () -reversed_ = prop "reversed" -{-# INLINE reversed_ #-} - -start_ :: JSString -> Html () -start_ = prop "start" -{-# INLINE start_ #-} - -align_ :: JSString -> Html () -align_ = prop "align" -{-# INLINE align_ #-} - -colspan_ :: JSString -> Html () -colspan_ = attr "colspan" -{-# INLINE colspan_ #-} - -rowspan_ :: JSString -> Html () -rowspan_ = attr "rowspan" -{-# INLINE rowspan_ #-} - -headers_ :: JSString -> Html () -headers_ = prop "headers" -{-# INLINE headers_ #-} - -scope_ :: JSString -> Html () -scope_ = prop "scope" -{-# INLINE scope_ #-} - -async_ :: JSString -> Html () -async_ = prop "async" -{-# INLINE async_ #-} - -charset_ :: JSString -> Html () -charset_ = prop "charset" -{-# INLINE charset_ #-} - -content_ :: JSString -> Html () -content_ = prop "content" -{-# INLINE content_ #-} - -defer_ :: JSString -> Html () -defer_ = prop "defer" -{-# INLINE defer_ #-} - -httpEquiv_ :: JSString -> Html () -httpEquiv_ = prop "httpEquiv" -{-# INLINE httpEquiv_ #-} - -language_ :: JSString -> Html () -language_ = prop "language" -{-# INLINE language_ #-} - -scoped_ :: JSString -> Html () -scoped_ = prop "scoped" -{-# INLINE scoped_ #-} - -type_ :: JSString -> Html () -type_ = prop "type" -{-# INLINE type_ #-} - -name_ :: JSString -> Html () -name_ = prop "name" -{-# INLINE name_ #-} - -href_ :: JSString -> Html () -href_ = prop "href" -{-# INLINE href_ #-} - -id_ :: JSString -> Html () -id_ = prop "id" -{-# INLINE id_ #-} - -placeholder_ :: JSString -> Html () -placeholder_ = prop "placeholder" -{-# INLINE placeholder_ #-} - -checked_ :: Bool -> Html () -checked_ = prop "checked" -{-# INLINE checked_ #-} - -autofocus_ :: Bool -> Html () -autofocus_ = prop "autofocus" -{-# INLINE autofocus_ #-} - -class_ :: JSString -> Html () -class_ = prop "className" -{-# INLINE class_ #-} - -data_ :: JSString -> JSString -> Html () -data_ k v = prop @JSString ("data-" <> k) v -{-# INLINE data_ #-} - -role_ :: JSString -> Html () -role_ = attr "role" -{-# INLINE role_ #-} - -style_ :: JSString -> Html () -style_ = prop "style" -{-# INLINE style_ #-} diff --git a/src/HtmlT/Types.hs b/src/HtmlT/Types.hs deleted file mode 100644 index 7fce8a2..0000000 --- a/src/HtmlT/Types.hs +++ /dev/null @@ -1,74 +0,0 @@ -module HtmlT.Types where - -import Control.Monad.Catch -import Control.Monad.Reader -import Data.Coerce -import Data.String -import GHC.Generics -import HtmlT.Event -import Control.Monad.Fix - -import JavaScript.Compat.Prim -import JavaScript.Compat.String (JSString(..)) - --- | HtmlT is nothing more than just a newtype over ReaderT HtmlEnv -newtype HtmlT m a = HtmlT {unHtmlT :: ReaderT HtmlEnv m a} - deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader HtmlEnv - , MonadFix, MonadCatch, MonadThrow, MonadMask, MonadTrans) - -data HtmlEnv = HtmlEnv - { html_current_element :: DOMElement - -- ^ A DOMElement that will be used as a parent to insert new - -- content, attributes, properties, listeners etc. - , html_content_boundary :: Maybe ContentBoundary - -- ^ Boundary defined by parent scope where new content should be - -- attached, when Nothing whole parent element is available - , html_reactive_env :: ReactiveEnv - -- ^ Needed to implement 'HasReactiveEnv' - } deriving Generic - --- | Most applications will only need HtmlT IO, hence this shortcut -type Html = HtmlT IO - --- | A newtype over JSVal which is an instance of Node --- https://developer.mozilla.org/en-US/docs/Web/API/Node -newtype DOMNode = DOMNode {unDOMNode :: JSVal} - --- | A newtype over JSVal which is an instance of HTMLElement --- https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement -newtype DOMElement = DOMElement {unDOMElement :: JSVal} - --- | A newtype over JSVal which is an instance of Event --- https://developer.mozilla.org/en-US/docs/Web/API/Event -newtype DOMEvent = DOMEvent {unDOMEvent :: JSVal} - --- | See https://developer.mozilla.org/en-US/docs/Web/Events for --- reference, what events are supported by particular elements -newtype EventName = EventName {unEventName :: JSString} - deriving newtype IsString - --- | Two comment nodes that define a boundary and a placeholder to --- insert additional nodes within the DOM. -data ContentBoundary = ContentBoundary - { boundary_begin :: DOMNode - , boundary_end :: DOMNode - } deriving Generic - --- | Each DOMElement is also a valid DOMNode -nodeFromElement :: DOMElement -> DOMNode -nodeFromElement = coerce - -runHtmlT :: HtmlT m a -> HtmlEnv -> m a -runHtmlT h = runReaderT (unHtmlT h) - -execHtmlT :: HtmlEnv -> HtmlT m a -> m a -execHtmlT = flip runHtmlT - -instance (Semigroup a, Applicative m) => Semigroup (HtmlT m a) where - (<>) = liftA2 (<>) - -instance (Monoid a, Applicative m) => Monoid (HtmlT m a) where - mempty = HtmlT $ ReaderT \_ -> pure mempty - -instance Monad m => HasReactiveEnv (HtmlT m) where - askReactiveEnv = asks html_reactive_env diff --git a/src/JavaScript/Compat/Foreign/Callback.hs b/src/JavaScript/Compat/Foreign/Callback.hs deleted file mode 100644 index 950f314..0000000 --- a/src/JavaScript/Compat/Foreign/Callback.hs +++ /dev/null @@ -1,135 +0,0 @@ -{-# LANGUAGE CPP #-} -#if defined(javascript_HOST_ARCH) -module JavaScript.Compat.Foreign.Callback - ( module GHC.JS.Foreign.Callback - ) where - -import GHC.JS.Foreign.Callback -#else - -module JavaScript.Compat.Foreign.Callback - ( Callback - , OnBlocked(..) - , releaseCallback - -- * asynchronous callbacks - , asyncCallback - , asyncCallback1 - , asyncCallback2 - , asyncCallback3 - -- * synchronous callbacks - , syncCallback - , syncCallback1 - , syncCallback2 - , syncCallback3 - -- * synchronous callbacks that return a value - , syncCallback' - , syncCallback1' - , syncCallback2' - , syncCallback3' - ) where - -import JavaScript.Compat.Prim - -data OnBlocked = ContinueAsync | ThrowWouldBlock deriving (Eq) - -data Callback a - -{- | - When you create a callback, the Haskell runtime stores a reference to - the exported IO action or function. This means that all data referenced by the - exported value stays in memory, even if nothing outside the Haskell runtime - holds a reference to to callback. - Use 'releaseCallback' to free the reference. Subsequent calls from JavaScript - to the callback will result in an exception. - -} -releaseCallback :: Callback a -> IO () -releaseCallback = undefined - -{- | Make a callback (JavaScript function) that runs the supplied IO action in a synchronous - thread when called. - Call 'releaseCallback' when done with the callback, freeing memory referenced - by the IO action. - -} -syncCallback :: OnBlocked -- ^ what to do when the thread blocks - -> IO () -- ^ the Haskell action - -> IO (Callback (IO ())) -- ^ the callback -syncCallback = undefined - - -{- | Make a callback (JavaScript function) that runs the supplied IO function in a synchronous - thread when called. The callback takes one argument that it passes as a JSVal value to - the Haskell function. - Call 'releaseCallback' when done with the callback, freeing data referenced - by the function. - -} -syncCallback1 :: OnBlocked -- ^ what to do when the thread blocks - -> (JSVal -> IO ()) -- ^ the Haskell function - -> IO (Callback (JSVal -> IO ())) -- ^ the callback -syncCallback1 = undefined - - -{- | Make a callback (JavaScript function) that runs the supplied IO function in a synchronous - thread when called. The callback takes two arguments that it passes as JSVal values to - the Haskell function. - Call 'releaseCallback' when done with the callback, freeing data referenced - by the function. - -} -syncCallback2 :: OnBlocked -- ^ what to do when the thread blocks - -> (JSVal -> JSVal -> IO ()) -- ^ the Haskell function - -> IO (Callback (JSVal -> JSVal -> IO ())) -- ^ the callback -syncCallback2 = undefined - -{- | Make a callback (JavaScript function) that runs the supplied IO function in a synchronous - thread when called. The callback takes three arguments that it passes as JSVal values to - the Haskell function. - Call 'releaseCallback' when done with the callback, freeing data referenced - by the function. - -} -syncCallback3 :: OnBlocked -- ^ what to do when the thread blocks - -> (JSVal -> JSVal -> JSVal -> IO ()) -- ^ the Haskell function - -> IO (Callback (JSVal -> JSVal -> JSVal -> IO ())) -- ^ the callback -syncCallback3 = undefined - -{- | Make a callback (JavaScript function) that runs the supplied IO action in a synchronous - thread when called. - Call 'releaseCallback' when done with the callback, freeing memory referenced - by the IO action. - -} -syncCallback' :: IO JSVal - -> IO (Callback (IO JSVal)) -syncCallback' = undefined - -syncCallback1' :: (JSVal -> IO JSVal) - -> IO (Callback (JSVal -> IO JSVal)) -syncCallback1' = undefined - -syncCallback2' :: (JSVal -> JSVal -> IO JSVal) - -> IO (Callback (JSVal -> JSVal -> IO JSVal)) -syncCallback2' = undefined - -syncCallback3' :: (JSVal -> JSVal -> JSVal -> IO JSVal) - -> IO (Callback (JSVal -> JSVal -> JSVal -> IO JSVal)) -syncCallback3' = undefined - -{- | Make a callback (JavaScript function) that runs the supplied IO action in an asynchronous - thread when called. - Call 'releaseCallback' when done with the callback, freeing data referenced - by the IO action. - -} -asyncCallback :: IO () -- ^ the action that the callback runs - -> IO (Callback (IO ())) -- ^ the callback -asyncCallback = undefined - -asyncCallback1 :: (JSVal -> IO ()) -- ^ the function that the callback calls - -> IO (Callback (JSVal -> IO ())) -- ^ the calback -asyncCallback1 = undefined - -asyncCallback2 :: (JSVal -> JSVal -> IO ()) -- ^ the Haskell function that the callback calls - -> IO (Callback (JSVal -> JSVal -> IO ())) -- ^ the callback -asyncCallback2 = undefined - -asyncCallback3 :: (JSVal -> JSVal -> JSVal -> IO ()) -- ^ the Haskell function that the callback calls - -> IO (Callback (JSVal -> JSVal -> JSVal -> IO ())) -- ^ the callback -asyncCallback3 = undefined - -#endif diff --git a/src/JavaScript/Compat/Marshal.hs b/src/JavaScript/Compat/Marshal.hs deleted file mode 100644 index a655697..0000000 --- a/src/JavaScript/Compat/Marshal.hs +++ /dev/null @@ -1,91 +0,0 @@ -{-| -Implement the missing functionality, which is likely to be included in -the standard library at some point in the future. --} -{-# LANGUAGE CPP #-} -{-# LANGUAGE ForeignFunctionInterface #-} -{-# LANGUAGE JavaScriptFFI #-} -module JavaScript.Compat.Marshal where - -import Data.Bool -import Data.Coerce -import Data.Maybe -import JavaScript.Compat.Prim -import JavaScript.Compat.String -import Unsafe.Coerce - -newtype Nullable v = Nullable {unNullable :: JSVal} - -nullableToMaybe :: Coercible v JSVal => Nullable v -> Maybe v -nullableToMaybe (Nullable jsval) - | isNull jsval = Nothing - | otherwise = Just (coerce jsval) - -maybeToNullable :: Coercible v JSVal => Maybe v -> Nullable v -maybeToNullable = Nullable . maybe jsNull coerce - -class FromJSVal v where fromJSVal :: JSVal -> IO (Maybe v) - -instance FromJSVal Int where - fromJSVal = pure . Just . fromJSInt - -instance FromJSVal JSVal where - fromJSVal = pure . Just - -instance FromJSVal v => FromJSVal (Maybe v) where - fromJSVal j = maybe (pure (Just Nothing)) fromJSVal $ - nullableToMaybe (Nullable j) - -instance FromJSVal v => FromJSVal [v] where - fromJSVal s = fmap (Just . catMaybes) . mapM fromJSVal =<< fromJSArray s - -class ToJSVal v where toJSVal :: v -> IO JSVal - -instance ToJSVal Int where - toJSVal = pure . toJSInt - -instance ToJSVal Bool where - toJSVal = pure . bool js_false js_true - -instance ToJSVal JSVal where - toJSVal = pure - -instance ToJSVal v => ToJSVal (Maybe v) where - toJSVal s = pure . unNullable . maybeToNullable =<< mapM toJSVal s - -instance ToJSVal v => ToJSVal [v] where - toJSVal s = toJSArray =<< mapM toJSVal s - -#if !defined(javascript_HOST_ARCH) -instance FromJSVal JSString where fromJSVal = undefined - -instance ToJSVal JSString where toJSVal = undefined - -instance FromJSVal Bool where fromJSVal = undefined - -js_true :: JSVal = undefined -js_false :: JSVal = undefined -js_isString :: JSVal -> JSVal = undefined -#else - -instance FromJSVal JSString where - fromJSVal jsval = do - let - isString = unsafeCoerce $ - js_bool (unsafeCoerce False) (unsafeCoerce True) (js_isString jsval) - return $ if isString then Just (JSString jsval) else Nothing - -instance ToJSVal JSString where - toJSVal = pure . unJSString - -instance FromJSVal Bool where - fromJSVal = pure . Just . unsafeCoerce . - js_bool (unsafeCoerce False) (unsafeCoerce True) - -foreign import javascript unsafe - "(() => true)" js_true :: JSVal -foreign import javascript unsafe - "(() => false)" js_false :: JSVal -foreign import javascript unsafe - "((s) => typeof s === 'string')" js_isString :: JSVal -> JSVal -#endif diff --git a/src/JavaScript/Compat/Prim.hs b/src/JavaScript/Compat/Prim.hs deleted file mode 100644 index 88ca50c..0000000 --- a/src/JavaScript/Compat/Prim.hs +++ /dev/null @@ -1,177 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE MagicHash #-} -{-# LANGUAGE DeriveDataTypeable #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE JavaScriptFFI #-} -{-# LANGUAGE GHCForeignImportPrim #-} -{-# LANGUAGE UnliftedFFITypes #-} -{-# LANGUAGE UnboxedTuples #-} - -#if defined(javascript_HOST_ARCH) -module JavaScript.Compat.Prim - ( module GHC.JS.Prim - ) where - -import GHC.JS.Prim -#else - -module JavaScript.Compat.Prim ( JSVal(..), JSVal# - , JSException(..) - , WouldBlockException(..) - , toIO - , resolve - , resolveIO - , mkJSException - , fromJSString - , toJSString - , toJSArray - , fromJSArray - , fromJSInt - , toJSInt - , isNull - , isUndefined - , jsNull - , getProp - , getProp' - , getProp# - , unsafeGetProp - , unsafeGetProp' - , unsafeGetProp# - , unpackJSString# - , unpackJSStringUtf8# - , unsafeUnpackJSString# - , unsafeUnpackJSStringUtf8# - , unpackJSStringUtf8## - , unsafeUnpackJSStringUtf8## - ) where - -import Data.Typeable (Typeable) - -import GHC.Prim -import qualified GHC.Exception as Ex -import qualified GHC.Exts as Exts - -{- - JSVal is a boxed type that can be used as FFI - argument or result. --} - -data JSVal = JSVal Addr# -type JSVal# = Addr# - -{- - When a JavaScript exception is raised inside - a safe or interruptible foreign call, it is converted - to a JSException - -} -data JSException = JSException JSVal String - deriving (Typeable) - -instance Ex.Exception JSException - -instance Show JSException where - show (JSException _ xs) = "JavaScript exception: " ++ xs - -toIO :: Exts.Any -> IO Exts.Any -toIO = undefined - -resolve :: JSVal# -> JSVal# -> Exts.Any -> IO () -resolve = undefined - -resolveIO :: JSVal# -> JSVal# -> IO Exts.Any -> IO () -resolveIO = undefined - -mkJSException :: JSVal -> IO JSException -mkJSException = undefined - -{- | Low-level conversion utilities for packages that cannot - depend on ghcjs-base - -} - -{- | returns an empty string if the JSVal does not contain - a string - -} -fromJSString :: JSVal -> String -fromJSString = undefined - -toJSString :: String -> JSVal -toJSString = undefined - -fromJSArray :: JSVal -> IO [JSVal] -fromJSArray = undefined - -toJSArray :: [JSVal] -> IO JSVal -toJSArray = undefined - -{- | returns zero if the JSVal does not contain a number - -} -fromJSInt :: JSVal -> Int -fromJSInt = undefined - -toJSInt :: Int -> JSVal -toJSInt = undefined - -isNull :: JSVal -> Bool -isNull = undefined - -isUndefined :: JSVal -> Bool -isUndefined = undefined - -jsNull :: JSVal -jsNull = undefined - -getProp :: JSVal -> String -> IO JSVal -getProp = undefined - --- | only safe on immutable object -unsafeGetProp :: JSVal -> String -> JSVal -unsafeGetProp = undefined - -getProp' :: JSVal -> JSVal -> IO JSVal -getProp' = undefined - --- | only safe on immutable object -unsafeGetProp' :: JSVal -> JSVal -> JSVal -unsafeGetProp' = undefined - - --- | only safe on immutable Addr# -getProp# :: JSVal -> Addr# -> IO JSVal -getProp# = undefined - --- | only safe on immutable Addr# and JSVal -unsafeGetProp# :: JSVal -> Addr# -> JSVal -unsafeGetProp# = undefined - -unpackJSString# :: Addr# -> IO JSVal -unpackJSString# = undefined - -unpackJSStringUtf8# :: Addr# -> IO JSVal -unpackJSStringUtf8# = undefined - -unpackJSStringUtf8## :: Addr# -> State# s -> (# State# s, JSVal# #) -unpackJSStringUtf8## = undefined - --- | only safe on immutable Addr# -unsafeUnpackJSString# :: Addr# -> JSVal -unsafeUnpackJSString# = undefined - --- | only safe on immutable Addr# -unsafeUnpackJSStringUtf8# :: Addr# -> JSVal -unsafeUnpackJSStringUtf8# = undefined - -unsafeUnpackJSStringUtf8## :: Addr# -> JSVal# -unsafeUnpackJSStringUtf8## = undefined - -{- | If a synchronous thread tries to do something that can only - be done asynchronously, and the thread is set up to not - continue asynchronously, it receives this exception. - -} -data WouldBlockException = WouldBlockException - deriving (Typeable) - -instance Show WouldBlockException where - show _ = "thread would block" - -instance Ex.Exception WouldBlockException -#endif diff --git a/src/JavaScript/Compat/String.hs b/src/JavaScript/Compat/String.hs deleted file mode 100644 index 5ca61ec..0000000 --- a/src/JavaScript/Compat/String.hs +++ /dev/null @@ -1,16 +0,0 @@ -{-| -Implement the missing functionality, which is likely to be included in -the standard library at some point in the future. --} -{-# LANGUAGE CPP #-} -#if defined(javascript_HOST_ARCH) -module JavaScript.Compat.String - ( module JavaScript.Compat.String.JavaScript - ) where -import JavaScript.Compat.String.JavaScript -#else -module JavaScript.Compat.String - ( module JavaScript.Compat.String.Native - ) where -import JavaScript.Compat.String.Native -#endif diff --git a/src/JavaScript/Compat/String/JavaScript.hs b/src/JavaScript/Compat/String/JavaScript.hs deleted file mode 100644 index 5d8a3b7..0000000 --- a/src/JavaScript/Compat/String/JavaScript.hs +++ /dev/null @@ -1,207 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE ForeignFunctionInterface #-} -{-# LANGUAGE JavaScriptFFI #-} -module JavaScript.Compat.String.JavaScript where - -import GHC.Exts as Exts -import JavaScript.Compat.Prim -import System.IO.Unsafe -import Unsafe.Coerce - -newtype JSString = JSString {unJSString :: JSVal} - -toJSValPure :: JSString -> JSVal -toJSValPure = unJSString - -fromJSValPure :: JSVal -> JSString -fromJSValPure = JSString - -instance Show JSString where - show = show . unpack - {-# INLINE show #-} - -instance Eq JSString where - (==) (JSString a) (JSString b) = unsafeCoerce $ - js_stringEq a b (unsafeCoerce True) (unsafeCoerce False) - {-# INLINE (==) #-} - -instance Ord JSString where - -- TODO: Could be made way faster once implemented in JavaScript - compare a b = unpack a `compare` unpack b - -instance IsString JSString where - fromString = JSString . toJSString - {-# INLINE fromString #-} - -instance Semigroup JSString where - (<>) = append - {-# INLINE (<>) #-} - -instance Monoid JSString where - mempty = empty - {-# INLINE mempty #-} - -empty :: JSString -empty = "" -{-# NOINLINE empty #-} - -pack :: String -> JSString -pack = JSString . toJSString -{-# INLINE pack #-} - -unpack :: JSString -> String -unpack = fromJSString . unJSString -{-# INLINE unpack #-} - -strip :: JSString -> JSString -strip = JSString . js_strip . unJSString -{-# INLINE strip #-} - -append :: JSString -> JSString -> JSString -append (JSString a) (JSString b) = JSString (js_append a b) -{-# INLINE append #-} - -stripPrefix :: JSString -> JSString -> Maybe JSString -stripPrefix (JSString str) (JSString prefix) = - let - jsVal = js_stripPrefix str prefix - in - if isUndefined jsVal then Nothing else Just (JSString jsVal) -{-# INLINE stripPrefix #-} - -breakOn :: JSString -> JSString -> (JSString, JSString) -breakOn (JSString sep) (JSString str) = - let - jsTuple = js_breakOn sep str - in - ( JSString (js_unsafeIndex 0 jsTuple) - , JSString (js_unsafeIndex 1 jsTuple) - ) -{-# INLINE breakOn #-} - -splitOn :: JSString -> JSString -> [JSString] -splitOn (JSString sep) (JSString str) = fmap JSString $ unsafePerformIO $ - fromJSArray (js_splitOn sep str) -{-# NOINLINE splitOn #-} - -intercalate :: JSString -> [JSString] -> JSString -intercalate (JSString sep) list = - let - jsList = unsafePerformIO $ toJSArray (fmap unJSString list) - in - JSString $ js_intercalate sep jsList -{-# NOINLINE intercalate #-} - -drop :: Int -> JSString -> JSString -drop n (JSString str) = JSString $ js_drop n str -{-# INLINE drop #-} - -take :: Int -> JSString -> JSString -take n (JSString str) = JSString $ js_take n str -{-# INLINE take #-} - -encodeURIComponent :: JSString -> JSString -encodeURIComponent = - JSString . js_encodeURIComponent . unJSString -{-# INLINE encodeURIComponent #-} - -decodeURIComponent :: JSString -> JSString -decodeURIComponent = - JSString . js_decodeURIComponent . unJSString -{-# INLINE decodeURIComponent #-} - -toLower :: JSString -> JSString -toLower = JSString . js_toLower . unJSString -{-# INLINE toLower #-} - -toUpper :: JSString -> JSString -toUpper = JSString . js_toUpper . unJSString -{-# INLINE toUpper #-} - -isInfixOf :: JSString -> JSString -> Bool -isInfixOf (JSString substr) (JSString str) = - unsafeCoerce $ js_bool (unsafeCoerce False) (unsafeCoerce True) (js_isInfixOf substr str) -{-# INLINE isInfixOf #-} - -null :: JSString -> Bool -null = (==empty) -{-# INLINE null #-} - -#if !defined(javascript_HOST_ARCH) -js_stringEq :: JSVal -> JSVal -> Exts.Any -> Exts.Any -> Exts.Any = undefined -js_strip :: JSVal -> JSVal = undefined -js_append :: JSVal -> JSVal -> JSVal = undefined -js_stripPrefix :: JSVal -> JSVal -> JSVal = undefined -js_splitOn :: JSVal-> JSVal -> JSVal = undefined -js_breakOn :: JSVal-> JSVal -> JSVal = undefined -js_unsafeIndex :: Int -> JSVal -> JSVal = undefined -js_intercalate :: JSVal -> JSVal -> JSVal = undefined -js_drop :: Int -> JSVal -> JSVal = undefined -js_take :: Int -> JSVal -> JSVal = undefined -js_encodeURIComponent :: JSVal -> JSVal = undefined -js_decodeURIComponent :: JSVal -> JSVal = undefined -js_toLower :: JSVal -> JSVal = undefined -js_toUpper :: JSVal -> JSVal = undefined -js_isInfixOf :: JSVal -> JSVal -> JSVal = undefined -js_bool :: Exts.Any -> Exts.Any -> JSVal -> Exts.Any = undefined -#else -foreign import javascript unsafe - "((lhs, rhs, ifeq, ifneq) => lhs == rhs ? ifeq : ifneq)" - js_stringEq :: JSVal -> JSVal -> Exts.Any -> Exts.Any -> Exts.Any -foreign import javascript unsafe - "((str) => str.replace(/^\\s+|\\s+$/g, ''))" - js_strip :: JSVal -> JSVal -foreign import javascript unsafe - "((a, b) => a + b)" - js_append :: JSVal -> JSVal -> JSVal -foreign import javascript unsafe - "((prefix, str) => {\ - if (str.startsWith(prefix)) {\ - return str.slice(prefix.length);\ - }\ - return undefined;\ - })" - js_stripPrefix :: JSVal -> JSVal -> JSVal -foreign import javascript unsafe - "((sep, str) => str.split(sep))" - js_splitOn :: JSVal -> JSVal -> JSVal -foreign import javascript unsafe - "((sep, str) => {\ - var index = str.indexOf(sep);\ - if (index !== -1) {\ - return [str.slice(0, index), str.slice(index)];\ - }\ - return [str, ''];\ - })" - js_breakOn :: JSVal -> JSVal -> JSVal -foreign import javascript unsafe - "((i, arr) => arr[i])" - js_unsafeIndex :: Int -> JSVal -> JSVal -foreign import javascript unsafe - "((sep, list) => list.join(sep))" - js_intercalate :: JSVal -> JSVal -> JSVal -foreign import javascript unsafe - "((n, str) => str.slice(n))" - js_drop :: Int -> JSVal -> JSVal -foreign import javascript unsafe - "((n, str) => str.slice(0, n))" - js_take :: Int -> JSVal -> JSVal -foreign import javascript unsafe - "encodeURIComponent" - js_encodeURIComponent :: JSVal -> JSVal -foreign import javascript unsafe - "decodeURIComponent" - js_decodeURIComponent :: JSVal -> JSVal -foreign import javascript unsafe - "((s) => s.toLowerCase())" - js_toLower :: JSVal -> JSVal -foreign import javascript unsafe - "((s) => s.toUpperCase())" - js_toUpper :: JSVal -> JSVal -foreign import javascript unsafe - "((substr, str) => str.includes(substr))" - js_isInfixOf :: JSVal -> JSVal -> JSVal -foreign import javascript unsafe - "((iffalse, iftrue, jsbool) => jsbool ? iftrue : iffalse)" - js_bool :: Exts.Any -> Exts.Any -> JSVal -> Exts.Any -#endif diff --git a/src/JavaScript/Compat/String/Native.hs b/src/JavaScript/Compat/String/Native.hs deleted file mode 100644 index fc6583b..0000000 --- a/src/JavaScript/Compat/String/Native.hs +++ /dev/null @@ -1,115 +0,0 @@ -module JavaScript.Compat.String.Native where - -import Data.Char qualified as C -import Data.Text (Text) -import Data.Text qualified as T -import GHC.Exts as Exts -import JavaScript.Compat.Prim - -newtype JSString = JSString {unJSString :: Text} - deriving newtype (Eq, Ord, Show, IsString, Semigroup, Monoid) - -toJSValPure :: JSString -> JSVal -toJSValPure = undefined - -fromJSValPure :: JSVal -> JSString -fromJSValPure = undefined - -empty :: JSString -empty = JSString T.empty -{-# INLINE empty #-} - -pack :: String -> JSString -pack = JSString . T.pack -{-# INLINE pack #-} - -unpack :: JSString -> String -unpack = T.unpack . unJSString -{-# INLINE unpack #-} - -strip :: JSString -> JSString -strip = JSString . T.strip . unJSString -{-# INLINE strip #-} - -append :: JSString -> JSString -> JSString -append (JSString a) (JSString b) = JSString $ T.append a b -{-# INLINE append #-} - -stripPrefix :: JSString -> JSString -> Maybe JSString -stripPrefix (JSString a) (JSString b) = - JSString <$> T.stripPrefix a b -{-# INLINE stripPrefix #-} - -breakOn :: JSString -> JSString -> (JSString, JSString) -breakOn (JSString a) (JSString b) = - let (c, d) = T.breakOn a b in (JSString c, JSString d) -{-# INLINE breakOn #-} - -splitOn :: JSString -> JSString -> [JSString] -splitOn (JSString a) (JSString b) = fmap JSString $ T.splitOn a b -{-# NOINLINE splitOn #-} - -intercalate :: JSString -> [JSString] -> JSString -intercalate (JSString s) l = JSString $ T.intercalate s $ fmap unJSString l -{-# INLINE intercalate #-} - -drop :: Int -> JSString -> JSString -drop n (JSString s) = JSString $ T.drop n s -{-# INLINE drop #-} - -take :: Int -> JSString -> JSString -take n (JSString s) = JSString $ T.take n s -{-# INLINE take #-} - -encodeURIComponent :: JSString -> JSString -encodeURIComponent = - JSString . T.pack . concatMap encodeChar . T.unpack . unJSString - where - encodeChar c - | C.isAlphaNum c = [c] - | c == ' ' = "+" - | otherwise = '%' : showHex (C.ord c) "" - -decodeURIComponent :: JSString -> JSString -decodeURIComponent = JSString . T.pack . decode . T.unpack . unJSString - where - decode [] = [] - decode ('%':x1:x2:xs) - | C.isHexDigit x1 && C.isHexDigit x2 = - C.chr (16 * digitToInt x1 + digitToInt x2) : decode xs - decode ('+':xs) = ' ' : decode xs - decode (x:xs) = x : decode xs - -showHex :: Int -> String -> String -showHex n acc - | n < 16 = intToDigit n : acc - | otherwise = let (q,r) = n `divMod` 16 in showHex q (intToDigit r : acc) - -digitToInt :: Char -> Int -digitToInt c - | '0' <= c && c <= '9' = fromEnum c - fromEnum '0' - | 'a' <= c && c <= 'f' = fromEnum c - fromEnum 'a' + 10 - | 'A' <= c && c <= 'F' = fromEnum c - fromEnum 'A' + 10 - | otherwise = error "digitToInt: not a digit" - -intToDigit :: Int -> Char -intToDigit n - | 0 <= n && n <= 9 = toEnum (fromEnum '0' + n) - | 10 <= n && n <= 15 = toEnum (fromEnum 'a' + n - 10) - | otherwise = error "intToDigit: not a digit" - -toLower :: JSString -> JSString -toLower = JSString . T.toLower . unJSString -{-# INLINE toLower #-} - -toUpper :: JSString -> JSString -toUpper = JSString . T.toUpper . unJSString -{-# INLINE toUpper #-} - -isInfixOf :: JSString -> JSString -> Bool -isInfixOf (JSString a) (JSString b) = T.isInfixOf a b -{-# INLINE isInfixOf #-} - -null :: JSString -> Bool -null = T.null . unJSString -{-# INLINE null #-} diff --git a/src/Wasm/Compat/Marshal.hs b/src/Wasm/Compat/Marshal.hs new file mode 100644 index 0000000..9edb9b5 --- /dev/null +++ b/src/Wasm/Compat/Marshal.hs @@ -0,0 +1,398 @@ +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE ForeignFunctionInterface #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE UnboxedTuples #-} +#if defined(wasm32_HOST_ARCH) +{-# LANGUAGE JavaScriptFFI #-} +#endif +module Wasm.Compat.Marshal where + +import Control.Monad +import Data.Maybe +import Data.String +import Data.Text qualified as Text +import Data.List qualified as List +import Data.Coerce +import Data.Kind +import Wasm.Compat.Prim +import GHC.Prim +import GHC.Ptr +import GHC.Int +import GHC.Generics as G +import GHC.IO +import Data.Text.Internal +import Data.Array.Byte +import Data.Word + +newtype Nullable v = Nullable {unNullable :: JSVal} + +nullableToMaybe :: Coercible v JSVal => Nullable v -> Maybe v +nullableToMaybe (Nullable j) + | js_isNull j > 0 = Nothing + | otherwise = Just (coerce j) + +maybeToNullable :: Coercible v JSVal => Maybe v -> Nullable v +maybeToNullable = Nullable . maybe js_null coerce + +newtype TypeResult = TypeResult {unTypeResult :: Int} + +pattern TypeNull, TypeBool, TypeNumber, TypeString, TypeArray, TypeObject :: TypeResult +pattern TypeNull = TypeResult 0 +pattern TypeBool = TypeResult 1 +pattern TypeNumber = TypeResult 2 +pattern TypeString = TypeResult 3 +pattern TypeArray = TypeResult 4 +pattern TypeObject = TypeResult 5 +-------------------------------------------------------------------------------- + +instance IsString JSString where fromString = toJSString + +instance Semigroup JSString where (<>) = js_concatStr + +instance Monoid JSString where mempty = js_emptyStr + +instance Eq JSString where (==) a b = fromJSString a == fromJSString b + +instance Show JSString where show = show . fromJSString + +instance Ord JSString where compare a b = fromJSString a `compare` fromJSString b +-------------------------------------------------------------------------------- + +class FromJSVal a where + fromJSVal :: JSVal -> IO (Maybe a) + default fromJSVal :: (Generic a, GFromJSVal (Rep a)) => JSVal -> IO (Maybe a) + fromJSVal = fmap (fmap G.to) . gFromJSVal + +class FromJSValPure v where fromJSValPure :: JSVal -> Maybe v + +instance {-# OVERLAPS #-} FromJSValPure v => FromJSVal v where + fromJSVal = pure . fromJSValPure + +instance FromJSValPure Int where + fromJSValPure j = case js_typeOf j of + TypeNumber -> Just (js_unsafeInt j) + _ -> Nothing + +instance FromJSValPure v => FromJSValPure (Maybe v) where + fromJSValPure j + = maybe (Just Nothing) fromJSValPure + $ nullableToMaybe (Nullable j) + +instance FromJSVal v => FromJSVal (Maybe v) where + fromJSVal j = maybe (pure (Just Nothing)) fromJSVal $ + nullableToMaybe (Nullable j) + +instance FromJSValPure JSVal where fromJSValPure = Just + +instance FromJSValPure JSString where + fromJSValPure j = case js_typeOf j of + TypeString -> Just (JSString j) + _ -> Nothing + +instance FromJSValPure Bool where + fromJSValPure j = case js_typeOf j of + TypeBool -> Just (js_unsafeBool j) + _ -> Nothing + +instance FromJSVal v => FromJSVal [v] where + fromJSVal s = case js_typeOf s of + TypeArray -> do + len <- js_arrayLength s + xs <- forM [0..(len - 1)] $ fromJSVal <=< js_arrayIndex s + return $ Just $ catMaybes xs + _ -> return Nothing + +instance FromJSVal Text where + fromJSVal j = case js_typeOf j of + TypeString -> fmap Just $ textFromJSString $ JSString j + _ -> pure Nothing + +instance (FromJSVal a, FromJSVal b) => FromJSVal (a, b) where + fromJSVal j = do + ma <- fromJSVal =<< js_arrayIndex j 0 + mb <- fromJSVal =<< js_arrayIndex j 1 + return $ liftA2 (,) ma mb + +instance (FromJSVal a, FromJSVal b, FromJSVal c) => FromJSVal (a, b, c) where + fromJSVal j = do + ma <- fromJSVal =<< js_arrayIndex j 0 + mb <- fromJSVal =<< js_arrayIndex j 1 + mc <- fromJSVal =<< js_arrayIndex j 2 + return $ (,,) <$> ma <*> mb <*> mc + +-------------------------------------------------------------------------------- +class ToJSVal a where + toJSVal :: a -> IO JSVal + default toJSVal :: (Generic a, GToJSVal (Rep a)) => a -> IO JSVal + toJSVal = gToJSVal . G.from + +class ToJSValPure v where toJSValPure :: v -> JSVal + +instance {-# OVERLAPS #-} ToJSValPure v => ToJSVal v where + toJSVal = pure . toJSValPure + +instance ToJSValPure Int where toJSValPure = js_intJSVal + +instance ToJSValPure Bool where toJSValPure = js_boolJSVal + +instance ToJSValPure JSVal where toJSValPure j = j + +instance ToJSValPure JSString where toJSValPure (JSString j) = j + +instance ToJSVal v => ToJSVal (Maybe v) where + toJSVal = fmap (unNullable . maybeToNullable) . mapM toJSVal + +instance ToJSValPure v => ToJSValPure (Maybe v) where + toJSValPure = unNullable . maybeToNullable . fmap toJSValPure + +instance ToJSVal v => ToJSVal [v] where + toJSVal s = do + arr <- js_newEmptyArray + forM_ s $ toJSVal >=> js_arrayPush arr + return arr + +instance ToJSVal Text where + toJSVal = fmap (\(JSString j) -> j) . textToJSString + +instance (ToJSVal a, ToJSVal b) => ToJSVal (a, b) where + toJSVal (a, b) = do + ja <- toJSVal a + jb <- toJSVal b + toJSVal [ja, jb] + +instance (ToJSVal a, ToJSVal b, ToJSVal c) => ToJSVal (a, b, c) where + toJSVal (a, b, c) = do + ja <- toJSVal a + jb <- toJSVal b + jc <- toJSVal c + toJSVal [ja, jb, jc] + +-------------------------------------------------------------------------------- + +class GFromJSVal (f :: Type -> Type) where + gFromJSVal :: JSVal -> IO (Maybe (f a)) + +instance GFromJSVal f => GFromJSVal (M1 m c f) where + gFromJSVal = fmap (fmap M1) . gFromJSVal @f + +instance GFromJSObject (x :*: y) => GFromJSVal (x :*: y) where + gFromJSVal kvs = gFromJSObject kvs + +instance {-# OVERLAPPING #-} FromJSVal a => GFromJSVal (S1 s (Rec0 a)) where + gFromJSVal = fmap (fmap (M1 . K1)) . fromJSVal @a +-------------------------------------------------------------------------------- + +class GToJSVal (f :: Type -> Type) where + gToJSVal :: f x -> IO JSVal + +instance GToJSVal f => GToJSVal (M1 m c f) where + gToJSVal (M1 f) = gToJSVal f + +instance GToJSObject (x :*: y) => GToJSVal (x :*: y) where + gToJSVal (x :*: y) = do + o <- js_newObject + gToJSObject (x :*: y) o + return o + +instance {-# OVERLAPPING #-} (ToJSVal a) => GToJSVal (S1 s (Rec0 a)) where + gToJSVal (M1 (K1 a)) = toJSVal a +-------------------------------------------------------------------------------- + +class GToJSObject (f :: Type -> Type) where + gToJSObject :: f x -> JSVal -> IO () + +instance (GToJSObject x, GToJSObject y) => GToJSObject (x :*: y) where + gToJSObject (x :*: y) o = gToJSObject x o >> gToJSObject y o + +instance (GToJSObject f) => GToJSObject (M1 m c f) where + gToJSObject (M1 a) o = gToJSObject a o + +instance {-# OVERLAPPING #-} (ToJSVal a, Selector s) => GToJSObject (S1 s (Rec0 a)) where + gToJSObject (M1 (K1 a)) o = do + v <- toJSVal a + js_assignProp o addr len v + where + key = Text.pack $ selName (undefined :: M1 S s (Rec0 a) x) + !(Text (ByteArray arr) off len) = key + addr = Ptr (byteArrayContents# arr) `plusPtr` off + +class GFromJSObject (f :: Type -> Type) where + gFromJSObject :: JSVal -> IO (Maybe (f x)) + +instance (GFromJSObject x, GFromJSObject y) => GFromJSObject (x :*: y) where + gFromJSObject kvs = do + x <- gFromJSObject kvs + y <- gFromJSObject kvs + return $ liftA2 (:*:) x y + +instance (GFromJSObject f) => GFromJSObject (M1 m c f) where + gFromJSObject = fmap (fmap M1) . gFromJSObject + +instance {-# OVERLAPPING #-} (FromJSVal a, Selector s) => GFromJSObject (S1 s (Rec0 a)) where + gFromJSObject kvs = js_getProp kvs addr len >>= fmap (fmap (M1 . K1)) . fromJSVal + where + key = Text.pack $ selName (undefined :: M1 S s (Rec0 a) x) + !(Text (ByteArray arr) off len) = key + addr = Ptr (byteArrayContents# arr) `plusPtr` off +-------------------------------------------------------------------------------- + +textToJSString :: Text -> IO JSString +textToJSString (Text (ByteArray arr) off len) = do + let addr = byteArrayContents# arr + js_decodeUtf8 (Ptr addr `plusPtr` off) len + +textFromJSString :: JSString -> IO Text +textFromJSString j = IO \s0 -> + let (# s1, len@(I# len#) #) = unIO (js_stringLength j) s0 + (# s2, marr #) = newByteArray# (len# *# 3#) s1 + (# s3, tlen #) = unIO (js_encodeUtf8 j (Ptr (mutableByteArrayContents# marr)) len) s2 + (# s4, arr #) = unsafeFreezeByteArray# marr s3 + in (# s4, (Text (ByteArray arr) 0 tlen) #) + +newtype UnsafeJavaScript = UnsafeJavaScript {unUnsafeJavaScript :: Text} + deriving newtype (IsString, Semigroup, Monoid) + +evalJavaScript :: UnsafeJavaScript -> IO JSVal +evalJavaScript rjs = do + let Text (ByteArray arr) off len = rjs.unUnsafeJavaScript + addr = byteArrayContents# arr + js_evalJavaScript (Ptr addr `plusPtr` off) len + +evalJavaScript1 :: ToJSVal arg0 => arg0 -> UnsafeJavaScript -> IO JSVal +evalJavaScript1 arg0 rjs = do + a0 <- toJSVal arg0 + let Text (ByteArray arr) off len = rjs.unUnsafeJavaScript + addr = byteArrayContents# arr + js_evalJavaScript1 a0 (Ptr addr `plusPtr` off) len + +evalJavaScript2 :: (ToJSVal arg0, ToJSVal arg1) => arg0 -> arg1 -> UnsafeJavaScript -> IO JSVal +evalJavaScript2 arg0 arg1 rjs = do + a0 <- toJSVal arg0 + a1 <- toJSVal arg1 + let Text (ByteArray arr) off len = rjs.unUnsafeJavaScript + addr = byteArrayContents# arr + js_evalJavaScript2 a0 a1 (Ptr addr `plusPtr` off) len + +evalJavaScript3 :: (ToJSVal arg0, ToJSVal arg1, ToJSVal arg2) => arg0 -> arg1 -> arg2 -> UnsafeJavaScript -> IO JSVal +evalJavaScript3 arg0 arg1 arg2 rjs = do + a0 <- toJSVal arg0 + a1 <- toJSVal arg1 + a2 <- toJSVal arg2 + let Text (ByteArray arr) off len = rjs.unUnsafeJavaScript + addr = byteArrayContents# arr + js_evalJavaScript3 a0 a1 a2 (Ptr addr `plusPtr` off) len + +evalJavaScript4 :: (ToJSVal arg0, ToJSVal arg1, ToJSVal arg2, ToJSVal arg3) => arg0 -> arg1 -> arg2 -> arg3 -> UnsafeJavaScript -> IO JSVal +evalJavaScript4 arg0 arg1 arg2 arg3 rjs = do + a0 <- toJSVal arg0 + a1 <- toJSVal arg1 + a2 <- toJSVal arg2 + a3 <- toJSVal arg3 + let Text (ByteArray arr) off len = rjs.unUnsafeJavaScript + addr = byteArrayContents# arr + js_evalJavaScript4 a0 a1 a2 a3 (Ptr addr `plusPtr` off) len + +#if !defined(wasm32_HOST_ARCH) + +js_true :: JSVal = undefined +js_false :: JSVal = undefined +js_null :: JSVal = undefined +js_emptyStr :: JSString = undefined +js_isString :: JSVal -> JSVal = undefined +js_isNull :: JSVal -> Int = undefined +js_typeOf :: JSVal -> TypeResult = undefined +js_unsafeInt :: JSVal -> Int = undefined +js_unsafeBool :: JSVal -> Bool = undefined +js_intJSVal :: Int -> JSVal = undefined +js_boolJSVal :: Bool -> JSVal = undefined +js_concatStr :: JSString -> JSString -> JSString = undefined +js_newEmptyArray :: IO JSVal = undefined +js_arrayPush :: JSVal -> JSVal -> IO () = undefined +js_arrayLength :: JSVal -> IO Int = undefined +js_arrayIndex :: JSVal -> Int -> IO JSVal = undefined +js_decodeUtf8 :: Ptr Word8 -> Int -> IO JSString = undefined +js_encodeUtf8 :: JSString -> Ptr Word8 -> Int -> IO Int = undefined +js_stringLength :: JSString -> IO Int = undefined +js_evalJavaScript :: Ptr Word8 -> Int -> IO JSVal = undefined +js_evalJavaScript1 :: JSVal -> Ptr Word8 -> Int -> IO JSVal = undefined +js_evalJavaScript2 :: JSVal -> JSVal -> Ptr Word8 -> Int -> IO JSVal = undefined +js_evalJavaScript3 :: JSVal -> JSVal -> JSVal -> Ptr Word8 -> Int -> IO JSVal = undefined +js_evalJavaScript4 :: JSVal -> JSVal -> JSVal -> JSVal -> Ptr Word8 -> Int -> IO JSVal = undefined +js_getProp :: JSVal -> Ptr Word8 -> Int -> IO JSVal = undefined +js_assignProp :: JSVal -> Ptr Word8 -> Int -> JSVal -> IO () = undefined +js_newObject :: IO JSVal = undefined + +#else + +foreign import javascript unsafe + "true" js_true :: JSVal +foreign import javascript unsafe + "false" js_false :: JSVal +foreign import javascript unsafe + "null" js_null :: JSVal +foreign import javascript unsafe + "''" js_emptyStr :: JSString +foreign import javascript unsafe + "typeof $1 === 'string'" js_isString :: JSVal -> JSVal +foreign import javascript unsafe + "($1 === null || $1 === undefined)" js_isNull :: JSVal -> Int +foreign import javascript unsafe + "if ($1 === undefined || $1 === null) return 0;\ + if (typeof $1 === 'boolean') return 1;\ + if (typeof $1 === 'number') return 2;\ + if (typeof $1 === 'string') return 3;\ + if (Array.isArray($1)) return 4;\ + return 5;" js_typeOf :: JSVal -> TypeResult +foreign import javascript unsafe + "$1" js_unsafeInt :: JSVal -> Int +foreign import javascript unsafe + "$1" js_unsafeBool :: JSVal -> Bool +foreign import javascript unsafe + "$1" js_intJSVal :: Int -> JSVal +foreign import javascript unsafe + "($1 ? true : false)" js_boolJSVal :: Bool -> JSVal +foreign import javascript unsafe + "$1 + $2" js_concatStr :: JSString -> JSString -> JSString +foreign import javascript unsafe + "[]" js_newEmptyArray :: IO JSVal +foreign import javascript unsafe + "$1.push($2)" js_arrayPush :: JSVal -> JSVal -> IO () +foreign import javascript unsafe + "$1.length" js_arrayLength :: JSVal -> IO Int +foreign import javascript unsafe + "$1[$2]" js_arrayIndex :: JSVal -> Int -> IO JSVal +foreign import javascript unsafe + "(new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $1, $2)))" + js_decodeUtf8 :: Ptr Word8 -> Int -> IO JSString +foreign import javascript unsafe + "(new TextEncoder()).encodeInto($1, new Uint8Array(__exports.memory.buffer, $2, $3)).written" + js_encodeUtf8 :: JSString -> Ptr Word8 -> Int -> IO Int +foreign import javascript unsafe + "$1.length" + js_stringLength :: JSString -> IO Int +foreign import javascript unsafe + "eval(new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $1, $2)))" + js_evalJavaScript :: Ptr Word8 -> Int -> IO JSVal +foreign import javascript unsafe + "eval(new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3)))($1)" + js_evalJavaScript1 :: JSVal -> Ptr Word8 -> Int -> IO JSVal +foreign import javascript unsafe + "eval(new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $3, $4)))($1, $2)" + js_evalJavaScript2 :: JSVal -> JSVal -> Ptr Word8 -> Int -> IO JSVal +foreign import javascript unsafe + "eval(new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $4, $5)))($1, $2, $3)" + js_evalJavaScript3 :: JSVal -> JSVal -> JSVal -> Ptr Word8 -> Int -> IO JSVal +foreign import javascript unsafe + "eval(new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $5, $6)))($1, $2, $3, $4)" + js_evalJavaScript4 :: JSVal -> JSVal -> JSVal -> JSVal -> Ptr Word8 -> Int -> IO JSVal +foreign import javascript unsafe + "$1[new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3))]" + js_getProp :: JSVal -> Ptr Word8 -> Int -> IO JSVal +foreign import javascript unsafe + "$1[new TextDecoder('utf8').decode(new Uint8Array(__exports.memory.buffer, $2, $3))] = $4;" + js_assignProp :: JSVal -> Ptr Word8 -> Int -> JSVal -> IO () +foreign import javascript unsafe + "{}" + js_newObject :: IO JSVal +#endif diff --git a/src/Wasm/Compat/Prim.hs b/src/Wasm/Compat/Prim.hs new file mode 100644 index 0000000..76af87c --- /dev/null +++ b/src/Wasm/Compat/Prim.hs @@ -0,0 +1,76 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE GHC2021 #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE UnboxedTuples #-} +{-# LANGUAGE UnliftedNewtypes #-} + +#if defined(wasm32_HOST_ARCH) +module Wasm.Compat.Prim ( + module GHC.Wasm.Prim +) where +import GHC.Wasm.Prim +#else +module Wasm.Compat.Prim ( + JSVal#(..), + JSVal(..), + freeJSVal, + JSString (..), + fromJSString, + toJSString, + JSException (..), + WouldBlockException (..), + PromisePendingException (..), + mkJSCallback, + runIO, + runNonIO, +) where + +import Control.Exception +import GHC.Exts +import GHC.Stable + +newtype JSVal# + = JSVal# (Any :: UnliftedType) + +data JSVal + = forall a . JSVal JSVal# (Weak# JSVal#) (StablePtr# a) + +freeJSVal :: JSVal -> IO () +freeJSVal _ = error "freeJSVal: only implmented on Wasm Backend" + +newtype JSString = JSString JSVal + +fromJSString :: JSString -> String +fromJSString _ = error "fromJSString: only implmented on Wasm Backend" + +toJSString :: String -> JSString +toJSString _ = error "toJSString: only implmented on Wasm Backend" + +newtype JSException = JSException JSVal + +instance Show JSException where + showsPrec _ _ = error "showsPrec @JSException: only implmented on Wasm Backend" + +instance Exception JSException + +data WouldBlockException + = WouldBlockException + deriving (Show) + +instance Exception WouldBlockException + +data PromisePendingException + = PromisePendingException + deriving (Show) + +instance Exception PromisePendingException + +mkJSCallback :: (StablePtr a -> IO JSVal) -> a -> IO JSVal +mkJSCallback _ _ = error "mkJSCallback: only implmented on Wasm Backend" + +runIO :: (JSVal -> a -> IO ()) -> IO a -> IO JSVal +runIO _ _ = error "runIO: only implmented on Wasm Backend" + +runNonIO :: (JSVal -> a -> IO ()) -> a -> IO JSVal +runNonIO _ _ = error "runNonIO: only implmented on Wasm Backend" +#endif diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..cde7f9c --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,13 @@ +const path = require('path'); + +module.exports = { + entry: './jsbits/index.js', + output: { + path: path.resolve(__dirname, 'dist-newstyle'), + filename: 'index.bundle.js', + }, + resolve: { + extensions: ['.ts', '.js'], + }, + devtool: false, +};