Commit 0b550c2f authored by Michael Hanus 's avatar Michael Hanus

Adapt to Bootstrap4

parent b3978372
{ {
"name": "spicey", "name": "spicey",
"version": "3.2.0", "version": "3.3.0",
"author": "Michael Hanus <mh@informatik.uni-kiel.de>", "author": "Michael Hanus <mh@informatik.uni-kiel.de>",
"synopsis": "A web application framework for Curry", "synopsis": "A web application framework for Curry",
"category": [ "Web", "Database" ], "category": [ "Web", "Database" ],
......
------------------------------------------------------------------------------
--- Some configurations where data is stored.
------------------------------------------------------------------------------
module Config.Storage where
import FilePath ( (</>) )
--- Prefix a file name with the directory where global form data
--- is stored during run-time.
inDataDir :: String -> String
inDataDir filename = "data" </> filename
...@@ -69,9 +69,9 @@ deploy: checkdeploy ...@@ -69,9 +69,9 @@ deploy: checkdeploy
cp -r public/* $(WEBSERVERDIR) cp -r public/* $(WEBSERVERDIR)
chmod -R go+rX $(WEBSERVERDIR) chmod -R go+rX $(WEBSERVERDIR)
# recreate directory for storing local session data: # recreate directory for storing local session data:
/bin/rm -rf $(WEBSERVERDIR)/data /bin/rm -rf $(WEBSERVERDIR)/sessiondata
mkdir -p $(WEBSERVERDIR)/data mkdir -p $(WEBSERVERDIR)/sessiondata
chmod 700 $(WEBSERVERDIR)/data chmod 700 $(WEBSERVERDIR)/sessiondata
$(WEBSERVERDIR)/spicey.cgi: src/*.curry src/*/*.curry $(WEBSERVERDIR)/spicey.cgi: src/*.curry src/*/*.curry
$(CPM) exec $(CURRY2CGI) --system="$(SYSTEM)" \ $(CPM) exec $(CURRY2CGI) --system="$(SYSTEM)" \
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
"wui2" : ">= 0.0.1" "wui2" : ">= 0.0.1"
}, },
"compilerCompatibility": { "compilerCompatibility": {
"pakcs": ">= 2.0.0", "pakcs": ">= 2.0.0, < 3.0.0",
"kics2": ">= 2.0.0" "kics2": ">= 2.0.0, < 3.0.0"
}, },
"sourceDirs": [ "src", "src/Model" ] "sourceDirs": [ "src", "src/Model" ]
} }
This diff is collapsed.
...@@ -6,19 +6,19 @@ ...@@ -6,19 +6,19 @@
} }
.spicey_label_for_type_string:before { .spicey_label_for_type_string:before {
content: url(../images/text.png); content: url(../img/text.png);
} }
.spicey_label_for_type_int:before { .spicey_label_for_type_int:before {
content: url(../images/number.png); content: url(../img/number.png);
} }
.spicey_label_for_type_calendarTime:before { .spicey_label_for_type_date:before {
content: url(../images/time.png); content: url(../img/time.png);
} }
.spicey_label_for_type_relation:before { .spicey_label_for_type_relation:before {
content: url(../images/foreign.png); content: url(../img/foreign.png);
} }
.type_string { .type_string {
...@@ -72,7 +72,7 @@ input, select { ...@@ -72,7 +72,7 @@ input, select {
/* Changes to default bootstrap style: */ /* Changes to default bootstrap style: */
body { body {
padding-top: 60px; padding-top: 80px;
padding-bottom: 40px; padding-bottom: 40px;
} }
...@@ -83,8 +83,8 @@ body { ...@@ -83,8 +83,8 @@ body {
.jumbotron h1 { .jumbotron h1 {
margin-top: 5px; margin-top: 5px;
margin-bottom: 10x; margin-bottom: 20x;
font-size: 40px; font-size: 50px;
} }
/* button with left-aligned text */ /* button with left-aligned text */
......
<svg class="bi bi-caret-down" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M3.204 5L8 10.481 12.796 5H3.204zm-.753.659l4.796 5.48a1 1 0 001.506 0l4.796-5.48c.566-.647.106-1.659-.753-1.659H3.204a1 1 0 00-.753 1.659z" clip-rule="evenodd"/>
</svg>
\ No newline at end of file
<svg class="bi bi-caret-up" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M3.204 11L8 5.519 12.796 11H3.204zm-.753-.659l4.796-5.48a1 1 0 011.506 0l4.796 5.48c.566.647.106 1.659-.753 1.659H3.204a1 1 0 01-.753-1.659z" clip-rule="evenodd"/>
</svg>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
...@@ -14,7 +14,6 @@ module Controller.SpiceySystem ...@@ -14,7 +14,6 @@ module Controller.SpiceySystem
import Global import Global
import ReadNumeric import ReadNumeric
import Config.Storage
import Config.UserProcesses import Config.UserProcesses
import System.Spicey import System.Spicey
import HTML.Base import HTML.Base
...@@ -38,7 +37,7 @@ loginFormDef = formDefWithID "Controller.SpiceySystem.loginFormDef" ...@@ -38,7 +37,7 @@ loginFormDef = formDefWithID "Controller.SpiceySystem.loginFormDef"
--- The data processed by the login form. --- The data processed by the login form.
loginViewData :: Global (SessionStore (Maybe String)) loginViewData :: Global (SessionStore (Maybe String))
loginViewData = loginViewData =
global emptySessionStore (Persistent (inDataDir "loginViewData")) global emptySessionStore (Persistent (inSessionDataDir "loginViewData"))
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
--- Controller for showing and selecting user processes. --- Controller for showing and selecting user processes.
......
...@@ -8,6 +8,8 @@ module System.Routes ...@@ -8,6 +8,8 @@ module System.Routes
where where
import HTML.Base import HTML.Base
import HTML.Styles.Bootstrap4 ( hrefNav )
import Config.RoutesData import Config.RoutesData
--generated in RoutesData --generated in RoutesData
...@@ -47,10 +49,10 @@ getRouteMenu = do ...@@ -47,10 +49,10 @@ getRouteMenu = do
getLinks ((name, matcher, _):restroutes) = getLinks ((name, matcher, _):restroutes) =
case matcher of case matcher of
Exact string -> if string == "login" Exact string -> if string == "login"
then getLinks restroutes then getLinks restroutes
else [(href ("?" ++ string) [htxt name])] else [(hrefNav ("?" ++ string) [htxt name])] :
: getLinks restroutes getLinks restroutes
Prefix s1 s2 -> [href ("?"++s1++"/"++s2) [htxt name]] Prefix s1 s2 -> [hrefNav ("?"++s1++"/"++s2) [htxt name]] :
: getLinks restroutes getLinks restroutes
_ -> getLinks restroutes _ -> getLinks restroutes
getLinks [] = [] getLinks [] = []
...@@ -18,8 +18,6 @@ import Global ...@@ -18,8 +18,6 @@ import Global
import HTML.Session import HTML.Session
import Config.Storage ( inDataDir )
-------------------------------------------------------------------------- --------------------------------------------------------------------------
--- The data associated to a user session. --- The data associated to a user session.
--- It contains formation about the login status of a user. --- It contains formation about the login status of a user.
...@@ -43,7 +41,7 @@ setUserLoginOfSession login (SD _) = SD login ...@@ -43,7 +41,7 @@ setUserLoginOfSession login (SD _) = SD login
--- Definition of the session state to store the login name (as a string). --- Definition of the session state to store the login name (as a string).
userSessionInfo :: Global (SessionStore UserSessionInfo) userSessionInfo :: Global (SessionStore UserSessionInfo)
userSessionInfo = userSessionInfo =
global emptySessionStore (Persistent (inDataDir "userSessionInfo")) global emptySessionStore (Persistent (inSessionDataDir "userSessionInfo"))
--- Gets the data of the current user session. --- Gets the data of the current user session.
getUserSessionInfo :: IO UserSessionInfo getUserSessionInfo :: IO UserSessionInfo
......
...@@ -25,6 +25,7 @@ module System.Spicey ( ...@@ -25,6 +25,7 @@ module System.Spicey (
) where ) where
import Char ( isSpace, isDigit ) import Char ( isSpace, isDigit )
import FilePath ( (</>) )
import Global import Global
import ReadShowTerm ( readsQTerm ) import ReadShowTerm ( readsQTerm )
import Time import Time
...@@ -32,10 +33,9 @@ import Time ...@@ -32,10 +33,9 @@ import Time
import Database.CDBI.Connection ( SQLResult ) import Database.CDBI.Connection ( SQLResult )
import HTML.Base import HTML.Base
import HTML.Session import HTML.Session
import HTML.Styles.Bootstrap3 import HTML.Styles.Bootstrap4
import HTML.WUI import HTML.WUI
import Config.Storage
import Config.UserProcesses import Config.UserProcesses
import System.Routes import System.Routes
import System.Processes import System.Processes
...@@ -117,9 +117,10 @@ confirmDeletionPage _ question = do ...@@ -117,9 +117,10 @@ confirmDeletionPage _ question = do
case ctrlargs of case ctrlargs of
(_:args) -> return $ (_:args) -> return $
[h3 [htxt question], [h3 [htxt question],
par [hrefButton (showControllerURL entity ("destroy":args)) [htxt "Yes"], par [hrefPrimSmButton (showControllerURL entity ("destroy":args))
[htxt "Yes"],
nbsp, nbsp,
hrefButton (showControllerURL entity ["list"]) [htxt "No"]]] hrefScndSmButton (showControllerURL entity ["list"]) [htxt "No"]]]
_ -> displayUrlError _ -> displayUrlError
...@@ -195,8 +196,8 @@ renderWUI _ title buttontag cancelurl _ hexp handler = ...@@ -195,8 +196,8 @@ renderWUI _ title buttontag cancelurl _ hexp handler =
[h1 [htxt title], [h1 [htxt title],
hexp, hexp,
breakline, breakline,
primButton buttontag (\env -> handler env >>= getPage), primSmButton buttontag (\env -> handler env >>= getPage), nbsp,
hrefButton cancelurl [htxt "Cancel"]] hrefScndSmButton cancelurl [htxt "Cancel"]]
--- A WUI for manipulating CalendarTime entities. --- A WUI for manipulating CalendarTime entities.
...@@ -258,7 +259,7 @@ spiceyTitle = "Spicey Application" ...@@ -258,7 +259,7 @@ spiceyTitle = "Spicey Application"
--- The home URL and brand shown at the left top of the main page. --- The home URL and brand shown at the left top of the main page.
spiceyHomeBrand :: (String, [HtmlExp]) spiceyHomeBrand :: (String, [HtmlExp])
spiceyHomeBrand = ("?", [homeIcon, htxt " Home"]) spiceyHomeBrand = ("?", [htxt " Home"])
--- The standard footer of the Spicey page. --- The standard footer of the Spicey page.
spiceyFooter :: [HtmlExp] spiceyFooter :: [HtmlExp]
...@@ -281,7 +282,7 @@ getPage viewblock = case viewblock of ...@@ -281,7 +282,7 @@ getPage viewblock = case viewblock of
msg <- getPageMessage msg <- getPageMessage
login <- getSessionLogin login <- getSessionLogin
lasturl <- getLastUrl lasturl <- getLastUrl
withSessionCookie $ bootstrapPage "." ["bootstrap.min","spicey"] withSessionCookie $ bootstrapPage favIcon cssIncludes jsIncludes
spiceyTitle spiceyHomeBrand routemenu (rightTopMenu login) spiceyTitle spiceyHomeBrand routemenu (rightTopMenu login)
0 [] [h1 [htxt spiceyTitle]] 0 [] [h1 [htxt spiceyTitle]]
(messageLine msg lasturl : viewblock ) spiceyFooter (messageLine msg lasturl : viewblock ) spiceyFooter
...@@ -293,14 +294,25 @@ getPage viewblock = case viewblock of ...@@ -293,14 +294,25 @@ getPage viewblock = case viewblock of
else HtmlStruct "header" [("class","pagemessage")] [htxt msg] else HtmlStruct "header" [("class","pagemessage")] [htxt msg]
rightTopMenu login = rightTopMenu login =
[[href "?login" (maybe [loginIcon, nbsp, htxt "Login"] [[hrefNav "?login" (maybe [htxt "Login"]
(\n -> [logoutIcon, nbsp, htxt "Logout" (\n -> [ htxt "Logout"
,htxt $ " (" , htxt $ " (" ++ n ++ ")"
,style "text-success" [userIcon] ])
,htxt $ " "++n++")"
])
login)]] login)]]
favIcon :: String
favIcon = "bt4" </> "img" </> "favicon.ico"
cssIncludes :: [String]
cssIncludes =
map (\n -> "bt4" </> "css" </> n ++ ".css")
["bootstrap.min", "spicey"]
jsIncludes :: [String]
jsIncludes =
map (\n -> "bt4" </> "js" </> n ++ ".js")
["jquery.slim.min", "bootstrap.bundle.min"]
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- Action performed when a "cancel" button is pressed. -- Action performed when a "cancel" button is pressed.
-- In this case, a message is shown. -- In this case, a message is shown.
...@@ -385,7 +397,8 @@ spTable items = table items `addClass` "table table-hover table-condensed" ...@@ -385,7 +397,8 @@ spTable items = table items `addClass` "table table-hover table-condensed"
--- Definition of the session state to store the page message (a string). --- Definition of the session state to store the page message (a string).
pageMessage :: Global (SessionStore String) pageMessage :: Global (SessionStore String)
pageMessage = global emptySessionStore (Persistent (inDataDir "pageMessage")) pageMessage =
global emptySessionStore (Persistent (inSessionDataDir "pageMessage"))
--- Gets the page message and delete it. --- Gets the page message and delete it.
getPageMessage :: IO String getPageMessage :: IO String
...@@ -404,7 +417,7 @@ setPageMessage msg = putSessionData pageMessage msg ...@@ -404,7 +417,7 @@ setPageMessage msg = putSessionData pageMessage msg
--- Definition of the session state to store the last URL (as a string). --- Definition of the session state to store the last URL (as a string).
lastUrls :: Global (SessionStore [String]) lastUrls :: Global (SessionStore [String])
lastUrls = global emptySessionStore (Persistent (inDataDir "lastUrls")) lastUrls = global emptySessionStore (Persistent (inSessionDataDir "lastUrls"))
--- Gets the list of URLs of the current session. --- Gets the list of URLs of the current session.
getLastUrls :: IO [String] getLastUrls :: IO [String]
......
...@@ -10,7 +10,7 @@ module View.SpiceySystem ...@@ -10,7 +10,7 @@ module View.SpiceySystem
where where
import HTML.Base import HTML.Base
import HTML.Styles.Bootstrap3 (defaultButton, hrefButton, primButton) import HTML.Styles.Bootstrap4 ( hrefScndSmButton, primSmButton, scndButton )
import Config.UserProcesses import Config.UserProcesses
import System.Processes import System.Processes
...@@ -25,11 +25,11 @@ loginView :: Maybe String -> [HtmlExp] ...@@ -25,11 +25,11 @@ loginView :: Maybe String -> [HtmlExp]
loginView currlogin = loginView currlogin =
case currlogin of case currlogin of
Nothing -> [h3 [htxt "Login as:"], Nothing -> [h3 [htxt "Login as:"],
textField loginfield "", textField loginfield "", nbsp,
defaultButton "Login" loginHandler] primSmButton "Login" loginHandler]
Just _ -> [h3 [htxt "Really logout?"], Just _ -> [h3 [htxt "Really logout?"],
primButton "Logout" logoutHandler, primSmButton "Logout" logoutHandler, nbsp,
hrefButton "?" [htxt "Cancel"]] hrefScndSmButton "?" [htxt "Cancel"]]
where where
loginfield free loginfield free
......
...@@ -33,7 +33,7 @@ generateControllersForEntity erdname allEntities ...@@ -33,7 +33,7 @@ generateControllersForEntity erdname allEntities
[ "Global", "Maybe", "Time" [ "Global", "Maybe", "Time"
, "HTML.Base", "HTML.Session", "HTML.WUI" , "HTML.Base", "HTML.Session", "HTML.WUI"
, erdname , erdname
, "Config.EntityRoutes", "Config.Storage" , "Config.UserProcesses" , "Config.EntityRoutes", "Config.UserProcesses"
, sessionInfoModule, authorizationModule, enauthModName, spiceyModule , sessionInfoModule, authorizationModule, enauthModName, spiceyModule
, entitiesToHtmlModule erdname , entitiesToHtmlModule erdname
, viewModuleName ename , viewModuleName ename
...@@ -264,7 +264,7 @@ newStore _ entity@(Entity entityName _) relationships allEntities = ...@@ -264,7 +264,7 @@ newStore _ entity@(Entity entityName _) relationships allEntities =
(applyF (globalModule "global") (applyF (globalModule "global")
[constF (sessionModule "emptySessionStore"), [constF (sessionModule "emptySessionStore"),
applyF (globalModule "Persistent") applyF (globalModule "Persistent")
[applyF (storageModule "inDataDir") [applyF (sessionModule "inSessionDataDir")
[string2ac $ "new" ++ entityName ++ "Store"]]])] [string2ac $ "new" ++ entityName ++ "Store"]]])]
...@@ -490,7 +490,7 @@ editStore erdname entity@(Entity entityName _) relationships allEntities = ...@@ -490,7 +490,7 @@ editStore erdname entity@(Entity entityName _) relationships allEntities =
(applyF (globalModule "global") (applyF (globalModule "global")
[constF (sessionModule "emptySessionStore"), [constF (sessionModule "emptySessionStore"),
applyF (globalModule "Persistent") applyF (globalModule "Persistent")
[applyF (storageModule "inDataDir") [applyF (sessionModule "inSessionDataDir")
[string2ac $ "edit" ++ entityName ++ "Store"]]])] [string2ac $ "edit" ++ entityName ++ "Store"]]])]
--- Computes the tuple type of the data to be stored and manipulated --- Computes the tuple type of the data to be stored and manipulated
......
...@@ -77,11 +77,15 @@ entitiesToHtmlModule :: String -> String ...@@ -77,11 +77,15 @@ entitiesToHtmlModule :: String -> String
entitiesToHtmlModule erdname = "View.EntitiesToHtml" entitiesToHtmlModule erdname = "View.EntitiesToHtml"
bootstrapModule :: String bootstrapModule :: String
bootstrapModule = "HTML.Styles.Bootstrap3" bootstrapModule = "HTML.Styles.Bootstrap4"
-- Name of hrefButton operation: -- Name of hrefButton operation:
hrefButtonName :: QName hrefButtonName :: QName
hrefButtonName = (bootstrapModule,"hrefButton") hrefButtonName = (bootstrapModule, "hrefPrimSmButton")
-- Name of hrefSmallButton operation:
hrefSmallButtonName :: QName
hrefSmallButtonName = (bootstrapModule, "hrefPrimBadge")
relatedRelation :: String -> Relationship -> String relatedRelation :: String -> Relationship -> String
relatedRelation en (Relationship _ [REnd en1 _ _, REnd en2 _ _]) = relatedRelation en (Relationship _ [REnd en1 _ _, REnd en2 _ _]) =
......
...@@ -16,7 +16,7 @@ import Spicey.Scaffolding ...@@ -16,7 +16,7 @@ import Spicey.Scaffolding
systemBanner :: String systemBanner :: String
systemBanner = systemBanner =
let bannerText = "Spicey Web Framework (Version " ++ packageVersion ++ let bannerText = "Spicey Web Framework (Version " ++ packageVersion ++
" of 24/10/19)" " of 31/07/20)"
bannerLine = take (length bannerText) (repeat '-') bannerLine = take (length bannerText) (repeat '-')
in bannerLine ++ "\n" ++ bannerText ++ "\n" ++ bannerLine in bannerLine ++ "\n" ++ bannerText ++ "\n" ++ bannerLine
...@@ -44,27 +44,26 @@ spiceyStructure pkgname = ...@@ -44,27 +44,26 @@ spiceyStructure pkgname =
ResourcePatchFile NoExec "package.json" (replacePackageName pkgname), ResourcePatchFile NoExec "package.json" (replacePackageName pkgname),
ResourcePatchFile NoExec "Makefile" patchMakeFile, ResourcePatchFile NoExec "Makefile" patchMakeFile,
Directory "src" [ Directory "src" [
ResourceFile NoExec "Main.curry", srcfile "Main.curry",
Directory "System" [ Directory "System" [
ResourceFile NoExec $ "System" </> "Spicey.curry", srcfile $ "System" </> "Spicey.curry",
ResourceFile NoExec $ "System" </> "Routes.curry", srcfile $ "System" </> "Routes.curry",
ResourceFile NoExec $ "System" </> "SessionInfo.curry", srcfile $ "System" </> "SessionInfo.curry",
ResourceFile NoExec $ "System" </> "Authorization.curry", srcfile $ "System" </> "Authorization.curry",
ResourceFile NoExec $ "System" </> "Authentication.curry", srcfile $ "System" </> "Authentication.curry",
ResourceFile NoExec $ "System" </> "Processes.curry", srcfile $ "System" </> "Processes.curry",
GeneratedFromERD createAuthorizations ], GeneratedFromERD createAuthorizations ],
Directory "View" [ Directory "View" [
ResourceFile NoExec $ "View" </> "SpiceySystem.curry", srcfile $ "View" </> "SpiceySystem.curry",
GeneratedFromERD createViews, GeneratedFromERD createViews,
GeneratedFromERD createHtmlHelpers ], GeneratedFromERD createHtmlHelpers ],
Directory "Controller" [ Directory "Controller" [
ResourceFile NoExec $ "Controller" </> "SpiceySystem.curry", srcfile $ "Controller" </> "SpiceySystem.curry",
GeneratedFromERD createControllers ], GeneratedFromERD createControllers ],
Directory "Model" [ Directory "Model" [
GeneratedFromERD createModels ], GeneratedFromERD createModels ],
Directory "Config" [ Directory "Config" [
ResourceFile NoExec $ "Config" </> "Storage.curry", srcfile $ "Config" </> "UserProcesses.curry",
ResourceFile NoExec $ "Config" </> "UserProcesses.curry",
GeneratedFromERD createRoutes, GeneratedFromERD createRoutes,
GeneratedFromERD createEntityRoutes ] GeneratedFromERD createEntityRoutes ]
], ],
...@@ -73,31 +72,29 @@ spiceyStructure pkgname = ...@@ -73,31 +72,29 @@ spiceyStructure pkgname =
], ],
Directory "public" [ Directory "public" [
ResourceFile NoExec $ "public" </> "index.html", ResourceFile NoExec $ "public" </> "index.html",
ResourceFile NoExec $ "public" </> "favicon.ico", Directory "bt4" [
Directory "css" [ Directory "css" [
ResourceFile NoExec $ "css" </> "bootstrap.min.css", bt4file $ "css" </> "bootstrap.min.css",
ResourceFile NoExec $ "css" </> "spicey.css" bt4file $ "css" </> "spicey.css"
], ],
Directory "js" [ Directory "js" [
ResourceFile NoExec $ "js" </> "bootstrap.min.js", bt4file $ "js" </> "bootstrap.bundle.min.js",
ResourceFile NoExec $ "js" </> "jquery.min.js" bt4file $ "js" </> "jquery.slim.min.js"
], ],
Directory "fonts" [ Directory "img" [
ResourceFile NoExec $ "fonts" </> "glyphicons-halflings-regular.eot", bt4file $ "img" </> "favicon.ico",
ResourceFile NoExec $ "fonts" </> "glyphicons-halflings-regular.svg", bt4file $ "img" </> "spicey-logo.png",
ResourceFile NoExec $ "fonts" </> "glyphicons-halflings-regular.ttf", bt4file $ "img" </> "text.png",
ResourceFile NoExec $ "fonts" </> "glyphicons-halflings-regular.woff", bt4file $ "img" </> "time.png",
ResourceFile NoExec $ "fonts" </> "glyphicons-halflings-regular.woff2" bt4file $ "img" </> "number.png",
], bt4file $ "img" </> "foreign.png"
Directory "images" [ ]
ResourceFile NoExec $ "images" </> "spicey-logo.png",
ResourceFile NoExec $ "images" </> "text.png",
ResourceFile NoExec $ "images" </> "time.png",
ResourceFile NoExec $ "images" </> "number.png",
ResourceFile NoExec $ "images" </> "foreign.png"
] ]
]