From 0ad0fd720bec467b4241e2a8f81e54970ab983ef Mon Sep 17 00:00:00 2001 From: venkat24 Date: Sun, 3 Sep 2017 08:39:19 +0530 Subject: [PATCH] Allow user to set App Name - Add XML Parser TagSoup to set the App Name in `config.xml` - Add UI components to receive App Name input --- codeworld-server/codeworld-server.cabal | 3 ++ codeworld-server/src/AndroidExport.hs | 54 ++++++++++++++++++------- codeworld-server/src/Main.hs | 6 ++- codeworld-server/src/Util.hs | 3 ++ web/css/codeworld.css | 9 +++++ web/env.html | 2 +- web/js/codeworld.js | 52 +++++++++++++++++++++--- 7 files changed, 107 insertions(+), 22 deletions(-) diff --git a/codeworld-server/codeworld-server.cabal b/codeworld-server/codeworld-server.cabal index 65acbc532..815d8d237 100644 --- a/codeworld-server/codeworld-server.cabal +++ b/codeworld-server/codeworld-server.cabal @@ -21,6 +21,7 @@ Executable codeworld-server base64-bytestring, bytestring, codeworld-compiler, + containers, cryptonite, data-default, directory, @@ -35,6 +36,8 @@ Executable codeworld-server regex-tdfa, snap-core, snap-server, + strict, + tagsoup, temporary, text, unix diff --git a/codeworld-server/src/AndroidExport.hs b/codeworld-server/src/AndroidExport.hs index a764453e1..a4cd3da29 100644 --- a/codeworld-server/src/AndroidExport.hs +++ b/codeworld-server/src/AndroidExport.hs @@ -18,38 +18,62 @@ module AndroidExport where -import System.Process -import System.Directory -import System.FilePath +import Data.Maybe +import qualified Data.Map as M +import System.Process +import System.Directory +import System.FilePath +import qualified System.IO.Strict as ST +import Text.HTML.TagSoup import Util -buildAndroid :: BuildMode -> ProgramId -> IO() -buildAndroid mode programId = do +buildAndroid :: BuildMode -> ProgramId -> AppProps -> IO () +buildAndroid mode programId appProps = do + let appName = fromJust $ M.lookup "appName" appProps initCordovaProject mode programId copySource mode programId + setAppName mode programId appName buildApk mode programId return () - initCordovaProject :: BuildMode -> ProgramId -> IO () initCordovaProject mode programId = do + let rootDir = androidRootDir mode + checkIfRootExists <- doesDirectoryExist rootDir + if not checkIfRootExists + then do + createDirectory $ androidRootDir mode + else return () let buildDir = androidBuildDir mode programId checkIfBuildExists <- doesDirectoryExist buildDir - case checkIfBuildExists of - True -> return () - False -> do + if not checkIfBuildExists + then do checkIfParentExists <- doesDirectoryExist $ androidRootDir mode sourceParent programId - case checkIfParentExists of - True -> return () - False -> do + if not checkIfParentExists + then do createDirectory $ androidRootDir mode sourceParent programId copyDirIfExists "android-template" (androidRootDir mode sourceBase programId) - return () + else return () + else return () copySource :: BuildMode -> ProgramId -> IO () -copySource mode programId = do - copyFile (buildRootDir mode targetFile programId) (androidBuildDir mode programId "www" "js" "runjs.js") +copySource mode programId = + copyFile + (buildRootDir mode targetFile programId) + (androidBuildDir mode programId "www" "js" "runjs.js") + +setAppName :: BuildMode -> ProgramId -> String -> IO () +setAppName mode programId appName = do + let configFileName = androidBuildDir mode programId "config.xml" + configContents <- ST.readFile configFileName + let tagSoup = parseTags configContents + let newNameTag = [TagOpen "name" [], TagText appName] + writeFile configFileName (renderTags $ newSoup tagSoup newNameTag) + where newSoup soup insertTag = takeWhile nameId soup + ++ insertTag + ++ drop 2 (dropWhile nameId soup) + nameId = (~/= (""::String)) buildApk :: BuildMode -> ProgramId -> IO () buildApk mode programId = do diff --git a/codeworld-server/src/Main.hs b/codeworld-server/src/Main.hs index bb68a3197..9632c3ebf 100644 --- a/codeworld-server/src/Main.hs +++ b/codeworld-server/src/Main.hs @@ -34,6 +34,7 @@ import qualified Data.ByteString.Lazy as LB import Data.Char (isSpace) import Data.List import Data.Maybe +import qualified Data.Map.Strict as M import Data.Monoid import qualified Data.Text as T import qualified Data.Text.IO as T @@ -361,6 +362,8 @@ exportAndroidHandler :: Snap() exportAndroidHandler = do mode <- getBuildMode Just source <- getParam "source" + maybeAppName <- getParam "appName" + let appName = BC.unpack $ fromMaybe (BC.pack "CodeWorld App") maybeAppName let programId = sourceToProgramId source deployId = sourceToDeployId source success <- liftIO $ do @@ -369,7 +372,8 @@ exportAndroidHandler = do writeDeployLink mode deployId programId compileIfNeeded mode programId unless success $ modifyResponse $ setResponseCode 500 - liftIO $ buildAndroid mode programId + let appProps = M.fromList [("appName", appName)] + liftIO $ buildAndroid mode programId appProps let result = CompileResult (unProgramId programId) (unDeployId deployId) writeLBS (encode result) diff --git a/codeworld-server/src/Util.hs b/codeworld-server/src/Util.hs index 7046bec8b..5b5674f57 100644 --- a/codeworld-server/src/Util.hs +++ b/codeworld-server/src/Util.hs @@ -30,6 +30,7 @@ import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Lazy as LB import Data.Maybe import Data.Monoid +import qualified Data.Map.Strict as M import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T @@ -48,6 +49,8 @@ newtype DeployId = DeployId { unDeployId :: Text } deriving Eq newtype DirId = DirId { unDirId :: Text} deriving Eq newtype ShareId = ShareId { unShareId :: Text } deriving Eq +type AppProps = M.Map String String + autocompletePath :: FilePath autocompletePath = "web/codeworld-base.txt" diff --git a/web/css/codeworld.css b/web/css/codeworld.css index ccf232820..c03dded6d 100644 --- a/web/css/codeworld.css +++ b/web/css/codeworld.css @@ -157,6 +157,15 @@ body { cursor: pointer; } +button { + border: none; +} + +button:disabled, +button[disabled] { + background-color: #cccccc !important; +} + .cw-button { border-radius: 4px; cursor: pointer; diff --git a/web/env.html b/web/env.html index eb9330875..0134b08b8 100644 --- a/web/env.html +++ b/web/env.html @@ -97,7 +97,7 @@   Stop -   Android +   Run diff --git a/web/js/codeworld.js b/web/js/codeworld.js index d69d13e5a..d43e890e0 100644 --- a/web/js/codeworld.js +++ b/web/js/codeworld.js @@ -690,11 +690,13 @@ function run(hash, dhash, msg, error) { runner.contentWindow.location.replace(loc); if (!!navigator.mediaDevices && !!navigator.mediaDevices.getUserMedia) { document.getElementById('startRecButton').style.display = ''; + document.getElementById('exportAndroidButton').style.display = ''; } } else { runner.contentWindow.location.replace('about:blank'); document.getElementById('runner').style.display = 'none'; document.getElementById('startRecButton').style.display = 'none'; + document.getElementById('exportAndroidButton').style.display = 'none'; } if (hash || msg) { @@ -732,14 +734,51 @@ function goto(line, col) { } function exportAndroid() { + sweetAlert({ + title: "App Information", + text: "App Name", + type: "input", + showCancelButton: true, + confirmButtonText: "Build App", + closeOnConfirm: false, + inputPlaceholder: "CodeWorld App" + }, + function(inputValue){ + if (inputValue === false) { + return false; + } + if (inputValue === "") { + swal.showInputError("Please enter a name for your app"); + return false; + } + compileAndExportAndroid({ + appName: inputValue, + }); + sweetAlert({ + title: "Please Wait", + text: "Your app is being built", + imageUrl: "https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif", + showConfirmButton: false, + allowOutsideClick: false, + allowEscapeKey: false + }); + }); +} + +function compileAndExportAndroid(appProps) { var src = window.codeworldEditor.getValue(); var data = new FormData(); data.append('source', src); data.append('mode', window.buildMode); + for(var prop in appProps) { + data.append(prop, appProps[prop]); + } + document.getElementById('exportAndroidButton').disabled = true; sendHttp('POST', 'exportAndroid', data, function(request) { if(request.status != 200) { - alert("Android build FAILED"); + sweetAlert("Android Build Failed", "Something went wrong!", "error"); + document.getElementById('exportAndroidButton').disabled = false; return; } var response = JSON.parse(request.response); @@ -748,17 +787,19 @@ function exportAndroid() { var data = new FormData(); data.append('hash', hash); data.append('mode', window.buildMode); - var props = {}; + var props = {}; props.responseType = "blob"; sendHttpWithProps('POST', 'getAndroid', data, props, function(request) { if(request.status != 200) { - alert("Android fetch FAILED"); + sweetAlert("Android Fetch Failed", "Something went wrong!", "error"); + document.getElementById('exportAndroidButton').disabled = false; return; } - console.log("Success"); + swal("App Built!", "Your CodeWorld app will now be downloaded", "success"); + var blob = request.response; - var d = new Date(); + var d = new Date(); var filename = 'codeworld_app_' + d.toDateString().split(' ').join('_') + '_' + d.getHours() +':'+ d.getMinutes() +':'+ d.getSeconds() @@ -774,6 +815,7 @@ function exportAndroid() { window.URL.revokeObjectURL(url); a.remove(); + document.getElementById('exportAndroidButton').disabled = false; }); });