mirror of
https://onedev.site.tesses.net/crosslang/crosslangextras
synced 2026-02-08 17:15:45 +00:00
Add the webapp launcher and syntax highlighter
This commit is contained in:
96
Tesses.CrossLang.App/src/components/packageitem.tcross
Normal file
96
Tesses.CrossLang.App/src/components/packageitem.tcross
Normal file
@@ -0,0 +1,96 @@
|
||||
func Components.PackageItem(tytd,item)
|
||||
{
|
||||
|
||||
var html = <div class="row">
|
||||
<div class="min">
|
||||
<img width="64" height="64" src={item.thumb} alt="Package thumbnail">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<span>{item.name}</span>
|
||||
</div>
|
||||
<div class="min">
|
||||
<span>{item.version}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<raw(Components.InstallButton(tytd,item.name,item.version))>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
return html;
|
||||
}
|
||||
/^
|
||||
Get whether package is installed
|
||||
version must be the current version as a Version not string
|
||||
returns 0 if not, 1 if installed or 2 if can update
|
||||
^/
|
||||
func PackageState(packages,name, version)
|
||||
{
|
||||
each(var item : packages)
|
||||
{
|
||||
if(item.Name == name) {
|
||||
if(item.Version < version) return 2;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
func Components.InstallButton(packages, name, version, $confirm)
|
||||
{
|
||||
var id = Crypto.Base64Encode(new ByteArray($"{name}-{version}")).Replace("=","");
|
||||
|
||||
var version = Version.Parse(version);
|
||||
|
||||
if(version != null)
|
||||
{
|
||||
|
||||
var state = PackageState(packages,name,version);
|
||||
return <div id={id}>
|
||||
<if(state == 0)>
|
||||
<true>
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="install",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Install</button>
|
||||
</true>
|
||||
<false>
|
||||
<if(confirm)>
|
||||
<true>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="confirm",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Yes Uninstall</button>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="cancel",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</true>
|
||||
<false>
|
||||
<if(state == 1)>
|
||||
<true>
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="uninstall",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Uninstall</button>
|
||||
</true>
|
||||
<false>
|
||||
<nav class="group split">
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="install",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML" class="left-round">
|
||||
<i>update</i>
|
||||
<span>Update</span>
|
||||
</button>
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="uninstall",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML" class="right-round square">
|
||||
<i>delete</i>
|
||||
</button>
|
||||
</nav>
|
||||
</false>
|
||||
</if>
|
||||
</false>
|
||||
</if>
|
||||
|
||||
</false>
|
||||
</if>
|
||||
</div>;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
53
Tesses.CrossLang.App/src/components/shell.tcross
Normal file
53
Tesses.CrossLang.App/src/components/shell.tcross
Normal file
@@ -0,0 +1,53 @@
|
||||
func Components.Shell(title, html,idx)
|
||||
{
|
||||
var pages = [
|
||||
{
|
||||
url="./",
|
||||
label = "Home",
|
||||
icon = "home",
|
||||
classStr=""
|
||||
},
|
||||
{
|
||||
url="./get-more-apps",
|
||||
label = "More Apps",
|
||||
icon = "deployed_code_update",
|
||||
classStr=""
|
||||
},
|
||||
{
|
||||
url="./settings",
|
||||
label = "Settings",
|
||||
icon = "settings",
|
||||
classStr=""
|
||||
}
|
||||
];
|
||||
|
||||
pages[idx].classStr = "active";
|
||||
|
||||
return
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CrossLang - {title}</title>
|
||||
<link rel="stylesheet" href={(/"beer.min.css").MakeRelative(mypage).ToString()}>
|
||||
<link rel="stylesheet" href={(/"theme.css").MakeRelative(mypage).ToString()}>
|
||||
<script src="htmx.min.js" defer></script>
|
||||
<script type="module" src={(/"beer.min.js").MakeRelative(mypage).ToString()} defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="left max">
|
||||
<each(var page : pages)>
|
||||
<a hx-get={page.url} hx-target="body" hx-push-url="true" class={page.classStr}>
|
||||
<i>{page.icon}</i>
|
||||
<span>{page.label}</span>
|
||||
</a>
|
||||
</each>
|
||||
</nav>
|
||||
<main class="responsive">
|
||||
<raw(html)>
|
||||
</main>
|
||||
</body>
|
||||
</html>;
|
||||
|
||||
}
|
||||
114
Tesses.CrossLang.App/src/main.tcross
Normal file
114
Tesses.CrossLang.App/src/main.tcross
Normal file
@@ -0,0 +1,114 @@
|
||||
var AppResources = [
|
||||
{path="/beer.min.css",value=embed("beer.min.css")},
|
||||
{path="/beer.min.js",value=embed("beer.min.js")},
|
||||
{path="/htmx.min.js",value=embed("htmx.min.js")},
|
||||
{path="/material-symbols-outlined.woff2",value=embed("material-symbols-outlined.woff2")},
|
||||
{path="/material-symbols-rounded.woff2",value=embed("material-symbols-rounded.woff2")},
|
||||
{path="/material-symbols-sharp.woff2",value=embed("material-symbols-sharp.woff2")},
|
||||
{path="/material-symbols-subset.woff2",value=embed("material-symbols-subset.woff2")},
|
||||
{path="/favicon.ico",value=embed("favicon.ico")},
|
||||
{path="/theme.css",value=embed("theme.css")}
|
||||
];
|
||||
var SERVER = (ctx)=>{
|
||||
if(ctx.Path == "/")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Home());
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/get-more-apps")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.GetMoreApps(ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/package-manage" && ctx.Method == "POST")
|
||||
{
|
||||
var data = {
|
||||
name = ctx.QueryParams.TryGetFirst("name"),
|
||||
version = ctx.QueryParams.TryGetFirst("version"),
|
||||
action = ctx.QueryParams.TryGetFirst("action")
|
||||
};
|
||||
|
||||
var mustConfirm = false;
|
||||
|
||||
|
||||
switch(data.action)
|
||||
{
|
||||
case "install":
|
||||
{
|
||||
var v = Version.Parse(data.version);
|
||||
if(v != null)
|
||||
PackageInstall(data.name,v);
|
||||
}
|
||||
break;
|
||||
case "uninstall":
|
||||
{
|
||||
mustConfirm = true;
|
||||
}
|
||||
break;
|
||||
case "confirm":
|
||||
{
|
||||
PackageUninstall(data.name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
ctx.WithMimeType("text/html").SendText(Components.InstallButton(GetPackages(), data.name, data.version, mustConfirm));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/settings")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Settings(ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/launch")
|
||||
{
|
||||
var app = ctx.QueryParams.TryGetFirst("app");
|
||||
if(app != null)
|
||||
{
|
||||
var path = GetWebAppsDir() / app / $"{app}.crvm";
|
||||
if(FS.Local.FileExists(path))
|
||||
{
|
||||
var env = VM.CreateEnvironment({});
|
||||
env.RegisterEverything();
|
||||
env.LockRegister();
|
||||
|
||||
env.LoadFileWithDependencies(FS.Local,path);
|
||||
var myArgs = [path.ToString()];
|
||||
|
||||
|
||||
SERVER = env.GetDictionary().WebAppMain(myArgs);
|
||||
}
|
||||
}
|
||||
ctx.StatusCode=302;
|
||||
ctx.WithLocationHeader("/");
|
||||
ctx.WithMimeType("text/html").SendText(<a href="./">Go Home</a>);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
each(var file : AppResources)
|
||||
{
|
||||
if(ctx.Path == file.path)
|
||||
{
|
||||
ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
func WebAppMain(args)
|
||||
{
|
||||
return {Handle = (ctx)=>{
|
||||
if(TypeIsCallable(SERVER)) return SERVER(ctx);
|
||||
else return SERVER.Handle(ctx);
|
||||
}, Close = ()=>{
|
||||
if(TypeIsCallable(SERVER)) return;
|
||||
SERVER.Close();
|
||||
}};
|
||||
}
|
||||
func main(args)
|
||||
{
|
||||
Console.WriteLine("use crosslang webapp-test to test");
|
||||
}
|
||||
|
||||
60
Tesses.CrossLang.App/src/pages/home.tcross
Normal file
60
Tesses.CrossLang.App/src/pages/home.tcross
Normal file
@@ -0,0 +1,60 @@
|
||||
func GetWebAppsDir()
|
||||
Env.CrossLangConfig / "WebApps";
|
||||
|
||||
enumerable func QueryApps()
|
||||
{
|
||||
var wad = GetWebAppsDir();
|
||||
if(FS.Local.DirectoryExists(wad))
|
||||
{
|
||||
each(var item : FS.Local.EnumeratePaths(wad))
|
||||
{
|
||||
if(FS.Local.DirectoryExists(item) && item.GetFileName() != "crosslang")
|
||||
{
|
||||
|
||||
var path = item/item.GetFileName() + ".crvm";
|
||||
|
||||
var strm = FS.Local.OpenFile(path, "rb");
|
||||
var vmFile = VM.LoadExecutable(strm);
|
||||
var info={};
|
||||
try {
|
||||
info = Json.Decode(vmFile.Info);
|
||||
} catch(ex) {
|
||||
|
||||
}
|
||||
var em = embed("icon.png");
|
||||
var fullName = info.short_name_pretty ?? info.short_name ?? item.GetFileName();
|
||||
yield {
|
||||
href = $"./launch?app={Net.Http.UrlEncode(item.GetFileName())}",
|
||||
name = fullName,
|
||||
icon = $"data:image/png;base64,{Crypto.Base64Encode(vmFile.Icon ?? em)}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
func Pages.Home()
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
var html = <div class="list">
|
||||
<each(var app : QueryApps())>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img src={app.icon} alt="Icon">
|
||||
</div>
|
||||
<div class="max">
|
||||
<a href={app.href}>{app.name}</a>
|
||||
</div>
|
||||
</div>
|
||||
</each>
|
||||
|
||||
</div>;
|
||||
|
||||
|
||||
return Components.Shell("Home",html,0);
|
||||
}
|
||||
65
Tesses.CrossLang.App/src/pages/moreapps.tcross
Normal file
65
Tesses.CrossLang.App/src/pages/moreapps.tcross
Normal file
@@ -0,0 +1,65 @@
|
||||
var pm = new Tesses.CrossLang.PackageManager();
|
||||
func Pages.GetMoreApps(ctx)
|
||||
{
|
||||
var installed=GetPackages();
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
if(TypeOf(q) != "String")
|
||||
{
|
||||
q = "";
|
||||
}
|
||||
var server = ctx.QueryParams.TryGetFirst("server");
|
||||
if(TypeOf(server) != "String") server = "https://cpkg.tesseslanguage.com/";
|
||||
var items=[];
|
||||
var items2 = [];
|
||||
|
||||
each(var item : pm.Search(q,{server,type="webapp"}))
|
||||
{
|
||||
items2.Add({
|
||||
name = item.packageName,
|
||||
version = item.version,
|
||||
url = $"{server}/package?name={Net.Http.UrlEncode(item.packageName)}",
|
||||
thumb = $"{server}/api/v1/package_icon.png?name={Net.Http.UrlEncode(item.packageName)}&version={Net.Http.UrlEncode(item.version)}"
|
||||
});
|
||||
}
|
||||
each(var item : pm.GetPackageServers())
|
||||
{
|
||||
items.Add({
|
||||
active = items.Count == 0,
|
||||
url = item
|
||||
});
|
||||
}
|
||||
|
||||
var html = <null>
|
||||
<form hx-get="./get-more-apps" hx-target="body" hx-push-url="true">
|
||||
|
||||
<div class="field suffix border round">
|
||||
<select name="server">
|
||||
<each(var item : items)>
|
||||
<if(item.active)>
|
||||
<true>
|
||||
<option value={item.url} selected>{item.url}</option>
|
||||
</true>
|
||||
<false>
|
||||
<option value={item.url}>{item.url}</option>
|
||||
</false>
|
||||
</if>
|
||||
</each>
|
||||
</select>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<div class="row no-space">
|
||||
<div class="field border left-round max">
|
||||
<input type="text" name="q" value={q}>
|
||||
</div>
|
||||
<button type="submit" class="large right-round min">Search</button>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<each(var item : items2)>
|
||||
<raw(Components.PackageItem(installed,item))>
|
||||
|
||||
</each>
|
||||
</null>;
|
||||
return Components.Shell("More Apps",html,1);
|
||||
}
|
||||
90
Tesses.CrossLang.App/src/pages/settings.tcross
Normal file
90
Tesses.CrossLang.App/src/pages/settings.tcross
Normal file
@@ -0,0 +1,90 @@
|
||||
func Pages.Settings(ctx)
|
||||
{
|
||||
var darkmode = null;
|
||||
var f = Env.CrossLangConfig / "prefs.json";
|
||||
var o = {};
|
||||
if(FS.Local.FileExists(f))
|
||||
{
|
||||
o = Json.Decode(FS.ReadAllText(FS.Local, f));
|
||||
darkmode = o.darkmode ?? null;
|
||||
}
|
||||
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
var action = ctx.QueryParams.TryGetFirst("action");
|
||||
var darkMode = ctx.QueryParams.TryGetFirst("darkmode");
|
||||
if(action == "darkmode")
|
||||
{
|
||||
o = o ?? {};
|
||||
switch(darkMode)
|
||||
{
|
||||
case "default":
|
||||
darkmode = null;
|
||||
o.darkmode = null;
|
||||
break;
|
||||
case "light":
|
||||
darkmode = false;
|
||||
o.darkmode = false;
|
||||
break;
|
||||
case "dark":
|
||||
darkmode = true;
|
||||
o.darkmode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FS.WriteAllText(FS.Local,f, Json.Encode(o,true));
|
||||
}
|
||||
|
||||
|
||||
var html =
|
||||
<null>
|
||||
<form hx-post="./settings" hx-target="body">
|
||||
<input type="hidden" name="action" value="darkmode">
|
||||
<fieldset>
|
||||
<legend>Light/Dark Preference (Needs a restart)</legend>
|
||||
<nav class="vertical">
|
||||
<label class="radio">
|
||||
|
||||
<if(darkmode == null)>
|
||||
<true>
|
||||
<input type="radio" name="darkmode" value="default" checked>
|
||||
</true>
|
||||
<false>
|
||||
<input type="radio" name="darkmode" value="default">
|
||||
</false>
|
||||
</if>
|
||||
<span>System Default</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<if(darkmode == false)>
|
||||
<true>
|
||||
<input type="radio" name="darkmode" value="light" checked>
|
||||
</true>
|
||||
<false>
|
||||
<input type="radio" name="darkmode" value="light">
|
||||
</false>
|
||||
</if>
|
||||
|
||||
<span>Light Mode</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<if(darkmode == true)>
|
||||
<true>
|
||||
<input type="radio" name="darkmode" value="dark" checked>
|
||||
</true>
|
||||
<false>
|
||||
<input type="radio" name="darkmode" value="dark">
|
||||
</false>
|
||||
</if>
|
||||
|
||||
<span>Dark Mode</span>
|
||||
</label>
|
||||
<button>Save</button>
|
||||
</nav>
|
||||
</fieldset>
|
||||
</form>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Settings", html,2);
|
||||
}
|
||||
53
Tesses.CrossLang.App/src/pkg.tcross
Normal file
53
Tesses.CrossLang.App/src/pkg.tcross
Normal file
@@ -0,0 +1,53 @@
|
||||
func GetPackages()
|
||||
{
|
||||
var packages=[];
|
||||
var dir = Env.CrossLangConfig / "WebApps";
|
||||
if(FS.Local.DirectoryExists(dir))
|
||||
each(var path : FS.Local.EnumeratePaths(dir))
|
||||
{
|
||||
var name = path.GetFileName();
|
||||
var crvm=path / name + ".crvm";
|
||||
if(FS.Local.FileExists(crvm))
|
||||
{
|
||||
var strm = FS.Local.OpenFile(crvm,"rb");
|
||||
packages.Add(VM.LoadExecutable(strm));
|
||||
strm.Close();
|
||||
}
|
||||
}
|
||||
return packages;
|
||||
}
|
||||
|
||||
func PackageInstall(name,version)
|
||||
{
|
||||
|
||||
each(var item : GetPackages())
|
||||
{
|
||||
if(item.Name == name)
|
||||
{
|
||||
if(item.Version >= version)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var dir = new SubdirFilesystem(FS.Local,Env.CrossLangConfig / "WebApps");
|
||||
pm.DownloadPlugin(dir,name,version);
|
||||
}
|
||||
func PackageUninstall(name)
|
||||
{
|
||||
each(var item : GetPackages())
|
||||
{
|
||||
if(item.Name == name)
|
||||
{
|
||||
var info = Json.Decode(item.Info);
|
||||
var pn = TypeOf(info.short_name) == "String" ? info.short_name : name;
|
||||
var pp = Env.CrossLangConfig / "WebApps" / pn;
|
||||
|
||||
|
||||
if(FS.Local.DirectoryExists(pp))
|
||||
FS.Local.DeleteDirectoryRecurse(pp);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user