Make it a little faster, queue is in sqlite now, fix db bug and more
This commit is contained in:
@@ -14,6 +14,6 @@
|
||||
"project_dependencies": [
|
||||
"..\/Tesses.YouTubeDownloader"
|
||||
],
|
||||
"version": "1.0.0.6-dev",
|
||||
"version": "1.0.0.0-dev",
|
||||
"compTime": "secure"
|
||||
}
|
||||
@@ -12,6 +12,10 @@
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<a href="./" class="button extend circle">
|
||||
<i>home</i>
|
||||
<span>Home</span>
|
||||
</a>
|
||||
<div class="row">
|
||||
<div class="max"></div>
|
||||
<div class="min">
|
||||
|
||||
@@ -2,6 +2,13 @@ const assets = ["<@ASSETS@>"];
|
||||
|
||||
const staticCacheName = "tytd-static-<@BUILD_TIME@>";
|
||||
|
||||
async function ThrowOnBadGateway(req)
|
||||
{
|
||||
const resp = await fetch(req);
|
||||
if(resp.status === 502) throw new Error("Bad Gateway");
|
||||
return resp;
|
||||
}
|
||||
|
||||
self.addEventListener('install',evt => {
|
||||
evt.waitUntil(
|
||||
caches.open(staticCacheName).then(cache =>{
|
||||
@@ -19,24 +26,29 @@ self.addEventListener('activate',(evt)=>{
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', evt => {
|
||||
const uri = new URL(evt.request.url);
|
||||
|
||||
evt.respondWith(
|
||||
|
||||
|
||||
caches.match(evt.request).then(cacheRes=>{
|
||||
return cacheRes || fetch(evt.request);
|
||||
}).catch(()=>{
|
||||
if(uri.pathname === '/queue-size')
|
||||
console.warn('SW cacheRes:', cacheRes);
|
||||
return cacheRes || ThrowOnBadGateway(evt.request);
|
||||
}).catch((err)=>{
|
||||
console.warn('SW Fallback:', err.message);
|
||||
if(evt.request.headers.has('HX-Request'))
|
||||
{
|
||||
return new Response('<span hx-trigger="every 5000ms" hx-target="this" hx-push-url="false" hx-indicator="none" hx-get="./queue-size" hx-swap="outerHTML" class="badge">?</span>',{
|
||||
headers: { 'Content-Type': 'text/html' }
|
||||
});
|
||||
|
||||
return new Response("",{
|
||||
status: 200,
|
||||
headers: {
|
||||
'HX-Redirect': "/offline.html"
|
||||
}
|
||||
});
|
||||
}
|
||||
if(uri.pathname === "/progress")
|
||||
else
|
||||
{
|
||||
return caches.match("/offline-progress.html");
|
||||
return caches.match('/offline.html');
|
||||
|
||||
}
|
||||
return caches.match('/offline.html');
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
func Components.DiscoverEntry(item)
|
||||
{
|
||||
switch(item.type)
|
||||
{
|
||||
case "video":
|
||||
return
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./video-thumbnail?v={Net.Http.UrlEncode(item.id)}"} width="144" width="120">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<a target="_blank" href={$"./watch?v={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
|
||||
</div>
|
||||
<div class="min">
|
||||
<span>{item.views} • {item.uploaded}</span>
|
||||
</div>
|
||||
<div class="min">
|
||||
<each(var part : item.author)>
|
||||
<if(TypeOf(part.navigationEndpoint) == "Dictionary")>
|
||||
<true>
|
||||
<a target="_blank" href={$"./channel?id={Net.Http.UrlEncode(part.navigationEndpoint.browseEndpoint.browseId)}"}>{part.text}</a>
|
||||
|
||||
</true>
|
||||
<false>
|
||||
<span>{part.text}</span>
|
||||
</false>
|
||||
</if>
|
||||
</each>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
break;
|
||||
case "channel":
|
||||
return
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./channel-thumbnail?id={Net.Http.UrlEncode(item.id)}"} width="144" width="144">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<a target="_blank" href={$"./channel?id={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
|
||||
</div>
|
||||
<div class="min">
|
||||
<span>{item.subs}</span>
|
||||
</div>
|
||||
<div class ="min">
|
||||
<p class="min">{item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
break;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
func Components.MusicAlbum(item)
|
||||
{
|
||||
return <div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./album-art?id={Net.Http.UrlEncode(item.id)}"} width="200" height="200">
|
||||
</div>
|
||||
<div class="min">
|
||||
<a href={$"./music-album?id={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
func Components.MusicArtist(item)
|
||||
{
|
||||
return <a href={$"./music-artist?id={Net.Http.UrlEncode(item.id)}"}>{item.name}</a>;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ func Components.QueueSZ(tytd)
|
||||
tytd.Mutex.Lock();
|
||||
|
||||
|
||||
var html = <span hx-trigger="every 10000ms" hx-target="this" hx-push-url="false" hx-indicator="none" hx-get="./queue-size" hx-swap="outerHTML" class="badge">{tytd.VideoQueueCount}</span>;
|
||||
var html = <b hx-trigger="every 10000ms" hx-target="this" hx-push-url="false" hx-indicator="none" hx-get="./queue-size" hx-swap="outerHTML">{tytd.VideoQueueCount}</b>;
|
||||
|
||||
tytd.Mutex.Unlock();
|
||||
return html;
|
||||
|
||||
@@ -21,34 +21,48 @@ func Components.Shell(title, html, page, $mypage)
|
||||
text="Home",
|
||||
icon="home",
|
||||
href=index,
|
||||
classStr=""
|
||||
classStr="",
|
||||
badge = ""
|
||||
},
|
||||
{
|
||||
text="Queue",
|
||||
icon="downloading",
|
||||
href=(/"queue").MakeRelative(mypage).ToString(),,
|
||||
classStr="",
|
||||
badge=<b hx-trigger="every 1000ms" hx-target="this" hx-push-url="false" hx-indicator="none" hx-get="./queue-size" hx-swap="outerHTML">
|
||||
?
|
||||
</b>
|
||||
},
|
||||
{
|
||||
text = "Downloads",
|
||||
icon = "download",
|
||||
href=(/"downloads").MakeRelative(mypage).ToString(),
|
||||
classStr=""
|
||||
classStr="",
|
||||
badge=""
|
||||
},
|
||||
{
|
||||
text = "Plugins",
|
||||
icon = "extension",
|
||||
href= (/"plugins").MakeRelative(mypage).ToString(),
|
||||
classStr=""
|
||||
classStr="",
|
||||
badge=""
|
||||
},
|
||||
{
|
||||
text = "Settings",
|
||||
icon = "settings",
|
||||
href= (/"settings").MakeRelative(mypage).ToString(),
|
||||
classStr=""
|
||||
classStr="",
|
||||
badge=""
|
||||
},
|
||||
{
|
||||
text = "Api",
|
||||
icon = "api",
|
||||
href = (/"api").MakeRelative(mypage).ToString(),
|
||||
classStr=""
|
||||
classStr="",
|
||||
badge=""
|
||||
}
|
||||
];
|
||||
pages[page].classStr = "active";
|
||||
pages[page].classStr = " primary-container";
|
||||
return <!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -65,8 +79,43 @@ func Components.Shell(title, html, page, $mypage)
|
||||
<script type="module" src={(/"beer.min.js").MakeRelative(mypage).ToString()} defer></script>
|
||||
</head>
|
||||
<body hx-indicator="#loading-indicator">
|
||||
|
||||
<nav class="bottom">
|
||||
<dialog class="left" id="leftpane">
|
||||
<header>
|
||||
<nav>
|
||||
<img class="circle large" src="/tytd-128.png">
|
||||
<h6 class="max">TYTD2025</h6>
|
||||
<button hx-on:click="ui('#leftpane')" class="transparent circle large">
|
||||
<i>close</i>
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="space"></div>
|
||||
<ul class="list">
|
||||
<each(var page : pages)>
|
||||
|
||||
<li class={$"wave round{page.classStr}"} hx-target="body" hx-push-url="true" hx-get={page.href}>
|
||||
<i>{page.icon}</i>
|
||||
<span class="max">{page.text}</span>
|
||||
<raw(page.badge)>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</each>
|
||||
|
||||
|
||||
</ul>
|
||||
</dialog>
|
||||
<header class="fixed primary-container">
|
||||
<nav>
|
||||
<button hx-on:click="ui('#leftpane')" class="circle transparent">
|
||||
<i>menu</i>
|
||||
</button>
|
||||
<h6 class="max">TYTD2025</h6>
|
||||
|
||||
</nav>
|
||||
</header>
|
||||
/*<nav class="bottom">
|
||||
<each(var page : pages)>
|
||||
<a hx-get={page.href} hx-target="body" hx-push-url="true" class={page.classStr}>
|
||||
<i>{page.icon}</i>
|
||||
@@ -81,7 +130,7 @@ func Components.Shell(title, html, page, $mypage)
|
||||
|
||||
</a>
|
||||
</each>
|
||||
</nav>
|
||||
</nav>*/
|
||||
|
||||
<main class="responsive">
|
||||
<raw(html)>
|
||||
|
||||
@@ -58,6 +58,11 @@ class TYTDApp {
|
||||
}
|
||||
public Handle(ctx)
|
||||
{
|
||||
if(ctx.Path == "/api/v1/resolutions.json")
|
||||
{
|
||||
ctx.SendJson(Resolution.Resolutions);
|
||||
return true;
|
||||
}
|
||||
if(ctx.Path == "/service_worker.js" || fileNames.Contains(ctx.Path))
|
||||
{
|
||||
const inm=ctx.RequestHeaders.TryGetFirst("If-None-Match");
|
||||
@@ -176,6 +181,10 @@ class TYTDApp {
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if(ctx.Path == "/queue-size")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(ctx.Path == "/sso")
|
||||
{
|
||||
const app = ctx.QueryParams.TryGetFirst("app") ?? "";
|
||||
@@ -570,6 +579,21 @@ class TYTDApp {
|
||||
ctx.WithMimeType("application/json").SendJson(this.TYTD.GetChannelContents(id,page-1,count));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/queue.json")
|
||||
{
|
||||
if(ctx.Method == "GET")
|
||||
{
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
var count = ctx.QueryParams.TryGetFirstInt("count");
|
||||
if(TypeOf(page)!="Long") page = 1;
|
||||
if(TypeOf(count)!="Long") count = 20;
|
||||
|
||||
|
||||
var result = { entries = TYTD.GetQueueItems(page-1,count)};
|
||||
ctx.WithMimeType("application/json").SendJson(result);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/downloads.json")
|
||||
{
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
@@ -605,7 +629,7 @@ class TYTDApp {
|
||||
|
||||
if(!this.TYTD.SendDatabase(ctx))
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized","<h1>You are not authorized to download the database</h1>",3,/"api/v1/database.db"));
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized","<h1>You are not authorized to download the database</h1>",4,/"api/v1/database.db"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -773,6 +797,24 @@ class TYTDApp {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/queue")
|
||||
{
|
||||
|
||||
if(ctx.Method == "DELETE")
|
||||
{
|
||||
const id = ctx.QueryParams.TryGetFirstInt("id");
|
||||
if(TypeIsLong(id))
|
||||
{
|
||||
this.TYTD.QueueRemoveItem(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.TYTD.QueueClear();
|
||||
}
|
||||
}
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Queue(this.TYTD,ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/downloads")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Downloads(this.TYTD,ctx));
|
||||
@@ -793,7 +835,7 @@ class TYTDApp {
|
||||
{
|
||||
if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx)))
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,3));
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,4));
|
||||
return true;
|
||||
}
|
||||
const user = ctx.QueryParams.TryGetFirst("user");
|
||||
@@ -871,7 +913,7 @@ class TYTDApp {
|
||||
{
|
||||
if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx)))
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,3));
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,4));
|
||||
return true;
|
||||
}
|
||||
if(ctx.Method == "POST")
|
||||
|
||||
@@ -72,5 +72,5 @@ func Pages.Admin(tytd,ctx)
|
||||
|
||||
</form>;
|
||||
|
||||
return Components.Shell("Admin Settings",html ,3);
|
||||
return Components.Shell("Admin Settings",html ,4);
|
||||
}
|
||||
@@ -8,5 +8,5 @@ func Pages.Api()
|
||||
</a>
|
||||
</ul>
|
||||
</null>;
|
||||
return Components.Shell("Api",html,4);
|
||||
return Components.Shell("Api",html,5);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func Pages.ApiV1()
|
||||
</fieldset>
|
||||
</each>
|
||||
</null>;
|
||||
return Components.Shell("Api V1",html,4);
|
||||
return Components.Shell("Api V1",html,5);
|
||||
}
|
||||
|
||||
var apiv1_routes = Json.Decode(embed("apiv1_routes.json").ToString());
|
||||
|
||||
@@ -45,5 +45,5 @@ const html = <null>
|
||||
</form>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Change your password",html,3);
|
||||
return Components.Shell("Change your password",html,4);
|
||||
}
|
||||
@@ -35,5 +35,5 @@ func Pages.ChannelInfo(tytd,ctx)
|
||||
title = res.authorName;
|
||||
}
|
||||
|
||||
return Components.Shell($"Channel {title}",html,1);
|
||||
return Components.Shell($"Channel {title}",html,2);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
func Pages.Discover(tytd,ctx)
|
||||
{
|
||||
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
var res = null;
|
||||
var q2 = "";
|
||||
if(TypeOf(q) == "String")
|
||||
{
|
||||
q2 = q;
|
||||
res = tytd.Discover(q);
|
||||
|
||||
}
|
||||
|
||||
var html = <null>
|
||||
<form hx-get="./discover" hx-target="body" hx-push-url="true">
|
||||
<div class="field large prefix round fill active">
|
||||
<i class="front">search</i>
|
||||
<input name="q" value={q2}>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
<if(res != null)>
|
||||
<true>
|
||||
<ul>
|
||||
<each(var item : res.items)>
|
||||
<li><raw(Components.DiscoverEntry(item))></li>
|
||||
</each>
|
||||
</ul>
|
||||
<button class="responsive">More</button>
|
||||
</true></null>;
|
||||
|
||||
|
||||
return Components.Shell($"Discover {q2}",html,1);
|
||||
}
|
||||
@@ -289,5 +289,5 @@ func Pages.Downloads(tytd,ctx)
|
||||
|
||||
|
||||
|
||||
return Components.Shell($"Downloads {q}",html,1);
|
||||
return Components.Shell($"Downloads {q}",html,2);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func Pages.EditUserList(tytd, ctx)
|
||||
</fieldset>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Edit users",html, 3);
|
||||
return Components.Shell("Edit users",html, 4);
|
||||
}
|
||||
|
||||
func Pages.EditUser(user)
|
||||
@@ -141,5 +141,5 @@ func Pages.EditUser(user)
|
||||
</if>;
|
||||
|
||||
|
||||
return Components.Shell("Edit user",html, 3);
|
||||
return Components.Shell("Edit user",html, 4);
|
||||
}
|
||||
@@ -35,5 +35,5 @@ func Pages.List(tytd,ctx)
|
||||
</null>;
|
||||
|
||||
|
||||
return Components.Shell($"Personal List {name}",html,1);
|
||||
return Components.Shell($"Personal List {name}",html,2);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
func Pages.Artist(music,ctx)
|
||||
{
|
||||
|
||||
var id = ctx.QueryParams.TryGetFirst("id");
|
||||
var res = music.GetArtistsAlbums(id);
|
||||
|
||||
if(TypeOf(id) != "String")
|
||||
{
|
||||
return Components.Shell("Error",<h1>Artist id not specified</h1>,pages);
|
||||
}
|
||||
|
||||
|
||||
var html = <null>
|
||||
<h1>{res.name}</h1>
|
||||
<ul>
|
||||
<each(var item : res.items)>
|
||||
<li><raw(Components.MusicAlbum(item))></li>
|
||||
</each>
|
||||
</ul>
|
||||
</null>;
|
||||
|
||||
|
||||
return Components.Shell($"Artist {res.name}",html,2);
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
func Pages.Artists(music,ctx)
|
||||
{
|
||||
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
var res = null;
|
||||
var q2 = "";
|
||||
if(TypeOf(q) == "String")
|
||||
{
|
||||
q2 = q;
|
||||
res = music.GetArtists(q);
|
||||
|
||||
}
|
||||
|
||||
var html = <null>
|
||||
<form hx-get="./music-artists" hx-target="body" hx-push-url="true">
|
||||
<div class="field large prefix round fill active">
|
||||
<i class="front">search</i>
|
||||
<input name="q" value={q2}>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
<if(res != null)>
|
||||
<true>
|
||||
<ul>
|
||||
<each(var item : res)>
|
||||
<li><raw(Components.MusicArtist(item))></li>
|
||||
</each>
|
||||
</ul>
|
||||
</true></null>;
|
||||
|
||||
|
||||
return Components.Shell($"Search Artists {q2}",html,2);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
func Pages.Music(ctx)
|
||||
{
|
||||
var html = <ul>
|
||||
<div><a href="./music-artists">Artists</a></div>
|
||||
<div><a href="./music-downloaded">My Music</a></div>
|
||||
</ul>;
|
||||
return Components.Shell("Music",html,2);
|
||||
}
|
||||
@@ -81,5 +81,5 @@ func Pages.NewUser(error)
|
||||
</form>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("New User",html ,3);
|
||||
return Components.Shell("New User",html ,4);
|
||||
}
|
||||
@@ -22,5 +22,5 @@ func Pages.YouTubeAnonyPlaylist(video_ids)
|
||||
<div class="max"></div>
|
||||
</div>
|
||||
</null>;
|
||||
return Components.Shell("Create playlist",html ,1);
|
||||
return Components.Shell("Create playlist",html ,2);
|
||||
}
|
||||
@@ -35,5 +35,5 @@ func Pages.PlaylistInfo(tytd,ctx)
|
||||
title = res.authorName;
|
||||
}
|
||||
|
||||
return Components.Shell($"Playlist {title}",html,1);
|
||||
return Components.Shell($"Playlist {title}",html,2);
|
||||
}
|
||||
@@ -75,5 +75,5 @@ func Pages.DownloadPlugins(tytd,ctx)
|
||||
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Download plugins",html ,2);
|
||||
return Components.Shell("Download plugins",html ,3);
|
||||
}
|
||||
@@ -20,5 +20,5 @@ func Pages.Plugins(tytd,ctx)
|
||||
</each>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Installed plugins", UserFlags.CanUsePlugins(userFlags) ? html : <h1>You can{"'"}t use plugins</h1> ,2);
|
||||
return Components.Shell("Installed plugins", UserFlags.CanUsePlugins(userFlags) ? html : <h1>You can{"'"}t use plugins</h1> ,3);
|
||||
}
|
||||
71
Tesses.YouTubeDownloader.Server/src/pages/queue.tcross
Normal file
71
Tesses.YouTubeDownloader.Server/src/pages/queue.tcross
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
func Pages.Queue(tytd,ctx)
|
||||
{
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
if(TypeOf(page) != "Long") page = 1;
|
||||
page--;
|
||||
|
||||
|
||||
enumerable func getitems()
|
||||
{
|
||||
each(var item : tytd.GetQueueItems(page,10))
|
||||
{
|
||||
tytd.PutVideoInfoIfNotExists(item.videoId);
|
||||
const info = tytd.GetVideo(item.videoId);
|
||||
|
||||
yield {
|
||||
Title = $"{info.title}-{item.videoId}-{item.resolution}",
|
||||
VideoId = item.videoId,
|
||||
Id = item.id,
|
||||
ChannelId = info.channelId,
|
||||
Channel = info.author
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const html = <null>
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
|
||||
</div>
|
||||
<div class="min">
|
||||
<button hx-delete="./queue" hx-target="body" hx-push-url="true" hx-confirm="Are you sure you want to clear the queue?">Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<each(var item : getitems())>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./api/v1/video-thumbnail?v={Net.Http.UrlEncode(item.VideoId)}"} width="144" width="120">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
{item.Title}
|
||||
</div>
|
||||
|
||||
<div class="min">
|
||||
<a hx-get={$"./channel?id={Net.Http.UrlEncode(item.ChannelId)}"} href={$"./channel?id={Net.Http.UrlEncode(item.ChannelId)}"} hx-target="body" hx-push-url="true">{item.Channel}</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button class="circle" hx-delete={$"./queue?page={page+1}"} hx-vals={{id = item.Id}} hx-target="body" hx-push-url="true" hx-confirm="Are you sure you want to delete this?"><i>delete</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</each>
|
||||
|
||||
<footer class="row center-align">
|
||||
|
||||
<button hx-get={$"./queue?page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./queue?page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
|
||||
|
||||
</footer>
|
||||
|
||||
</null>;
|
||||
return Components.Shell("Queue",html,1);
|
||||
}
|
||||
@@ -15,5 +15,5 @@ func Pages.Settings(tytd,ctx)
|
||||
|
||||
</fieldset>;
|
||||
|
||||
return Components.Shell("Settings",html ,3);
|
||||
return Components.Shell("Settings",html ,4);
|
||||
}
|
||||
@@ -27,5 +27,5 @@ func Pages.VideoInfo(tytd,ctx)
|
||||
</div>;
|
||||
}
|
||||
|
||||
return Components.Shell(vi != null ? vi.title : "Could not find video", html, 1);
|
||||
return Components.Shell(vi != null ? vi.title : "Could not find video", html, 2);
|
||||
}
|
||||
@@ -11,5 +11,5 @@ func Pages.WhoAmI(row)
|
||||
<li>Can manage plugins: {UserFlags.CanManagePlugins(row.flags)}</li>
|
||||
</ul>
|
||||
</null>;
|
||||
return Components.Shell("Settings",html ,3);
|
||||
return Components.Shell("Settings",html ,4);
|
||||
}
|
||||
Reference in New Issue
Block a user