Compare commits

..
6 Commits
10 changed files with 502 additions and 72 deletions

No files matched your search

+2 -4
View File
@@ -1,6 +1,4 @@
def main(): import uvicorn
print("Hello from qrcode-from-onetab!")
if __name__ == "__main__": if __name__ == "__main__":
main() uvicorn.run("uhhhhup:app", host="0.0.0.0", port=2222, reload=True)
+258
View File
@@ -0,0 +1,258 @@
:root {
--bg-color: #121212;
--text-color: #e0e0e0;
--primary-color: #bb86fc;
--secondary-color: #03dac6;
--error-color: #cf6679;
--surface-color: #1e1e1e;
--border-radius: 8px;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100dvh;
box-sizing: border-box; /* Ensure padding is included in width/height */
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
button,
input[type="submit"],
.btn {
background-color: var(--surface-color);
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 10px 20px;
border-radius: var(--border-radius);
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s, color 0.2s;
margin: 5px;
display: inline-block;
text-align: center;
text-decoration: none;
}
button:hover,
input[type="submit"]:hover,
.btn:hover {
background-color: var(--primary-color);
color: var(--bg-color);
text-decoration: none;
}
textarea {
background-color: var(--surface-color);
color: var(--text-color);
border: 1px solid #333;
border-radius: var(--border-radius);
padding: 10px;
width: 100%;
max-width: 800px;
font-family: monospace;
min-width: 20%;
min-height: 20dvh;
}
form {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 800px;
gap: 10px;
}
ol {
list-style: none;
padding: 0;
width: 100%;
max-width: 800px;
}
li {
background-color: var(--surface-color);
margin-bottom: 10px;
padding: 15px;
border-radius: var(--border-radius);
word-break: break-all;
width: min-content;
}
li a {
display: flex;
flex-direction: column;
gap: 10px;
}
.qrcode {
margin-top: 10px;
background-color: white;
padding: 10px;
border-radius: 4px;
width: fit-content;
}
/* Specifics for reader */
#b {
font-size: 1.5rem;
padding: 20px 40px;
margin-top: 20px;
}
/* Specifics for index */
.hero {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
width: 100%;
max-width: 1000px;
margin-bottom: 40px;
}
.card {
background-color: var(--surface-color);
padding: 30px;
border-radius: var(--border-radius);
flex: 1;
min-width: 300px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
border: 1px solid #333;
/* max-height: 90dvh; Prevent card from exceeding viewport height */
overflow-y: auto; /* Scroll if content is too tall */
}
.card h2 {
color: var(--secondary-color);
margin-top: 0;
/* margin-bottom: 10px; Reduced margin */
/* font-size: 1.2rem; Slightly smaller title */
}
.card p {
margin-bottom: 15px; /* Reduced margin */
color: #aaa;
}
.card .actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
details {
width: 100%;
max-width: 600px;
margin-top: 20px;
background-color: var(--surface-color);
border-radius: var(--border-radius);
border: 1px solid #333;
}
summary {
padding: 15px;
cursor: pointer;
font-weight: bold;
color: var(--secondary-color);
list-style: none; /* Hide default triangle */
display: flex;
justify-content: space-between;
align-items: center;
}
summary::after {
content: '+';
font-size: 1.2rem;
}
details[open] summary::after {
content: '-';
}
details[open] summary {
border-bottom: 1px solid #333;
}
.route-list {
display: grid;
gap: 10px;
width: 100%;
padding: 15px;
box-sizing: border-box;
}
.route-item {
background-color: var(--surface-color);
padding: 15px;
border-radius: var(--border-radius);
display: flex;
justify-content: space-between;
align-items: center;
font-family: monospace;
}
/* Queue Grid */
.queue-grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
width: 100%;
}
.queue-grid li {
flex: 1 1 300px; /* Grow, shrink, basis */
max-width: 400px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.queue-grid li a {
align-items: center;
word-break: break-word;
}
/* Trail Page */
.trail-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 80dvh;
width: 100%;
cursor: pointer;
text-align: center;
}
.trail-count {
font-size: 5rem;
font-weight: bold;
color: var(--secondary-color);
}
.trail-hint {
color: #666;
margin-top: 20px;
}
+42 -16
View File
@@ -3,23 +3,49 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Queue Reader</title> <title>Document</title>
<link
rel="stylesheet"
href="{{ url_for('static', path='style.css') }}"
/>
</head> </head>
<body> <body>
{% from 'macros.html' import magic_link %} {% if current_link %} <h1>Queue Manager</h1>
<p>{{ magic_link(current_link, current_link, "a") }}</p>
<button id="b">skip/fw</button> <div class="hero">
<script> <div class="card">
document.getElementById("b").addEventListener("click", async () => { <h2>Input (PC)</h2>
await fetch("/r", { method: "GET", redirect: "manual" }); <p>
setTimeout(() => { Paste your OneTab export or list of links here to add them
window.location.reload(); to the queue.
}); </p>
}); <div class="actions">
</script> <a href="/queuein" class="btn">Add Links</a>
{% else %} </div>
<p>no</p> </div>
<button onclick="location.reload();">reload</button>
{% endif %} <div class="card">
<h2>Output (Mobile)</h2>
<p>Open these on your phone to consume the queue.</p>
<div class="actions">
<a href="/q" class="btn">Reader (QR)</a>
<a href="/t" class="btn">Trail (Focus)</a>
<a href="/r" class="btn">Next Link</a>
</div>
</div>
</div>
<details >
<summary>System Routes</summary>
<div class="route-list">
{% macro this(route) %}
<div class="route-item">
<a href="{{ url_for(route.name) }}">{{ route.name }}</a>
<span>{{ route.path }}</span>
</div>
{% endmacro %} {% for route in routes if "GET" in route.methods %}
{{ this(route) }} {% endfor %}
</div>
</details>
</body> </body>
</html> </html>
+3 -9
View File
@@ -24,7 +24,7 @@
})(); })();
</script> </script>
{% endmacro %} {% macro qrcode_queue(queue_items) %} {% if queue_items %} {% endmacro %} {% macro qrcode_queue(queue_items) %} {% if queue_items %}
<ol> <ol class="queue-grid">
{% for item in queue_items %} {% for item in queue_items %}
<li> <li>
<a href="{{ item }}" target="_blank" rel="noreferrer noopener"> <a href="{{ item }}" target="_blank" rel="noreferrer noopener">
@@ -33,14 +33,6 @@
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
<style>
/* it works ig */
& {
li:has(.qrcode) {
width: min-content;
}
}
</style>
</ol> </ol>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/qrcode.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/qrcode.js"></script>
<script> <script>
@@ -50,6 +42,8 @@
new QRCode(e, { new QRCode(e, {
text: a, text: a,
correctLevel: QRCode.CorrectLevel.L, correctLevel: QRCode.CorrectLevel.L,
width: 256,
height: 256,
}); });
}); });
}); });
+13 -14
View File
@@ -4,11 +4,6 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Queue dump type shit</title> <title>Queue dump type shit</title>
<style>
li {
width: min-content;
}
</style>
</head> </head>
<body> <body>
@@ -34,9 +29,9 @@
<script> <script>
/** @type {HTMLDivElement} */ /** @type {HTMLDivElement} */
const bij = document.getElementById("gonk"); const bij = document.getElementById("gonk");
let arrlen = 0;
/** @type {HTMLInputElement} */ /** @type {HTMLInputElement} */
const n = document.getElementById("n"); const n = document.getElementById("n");
const changeHandler = async () => { const changeHandler = async () => {
const number = parseInt(n.value); const number = parseInt(n.value);
if (isNaN(number)) { if (isNaN(number)) {
@@ -46,12 +41,11 @@
const f = new URL("/api/queue", window.location.href); const f = new URL("/api/queue", window.location.href);
f.searchParams.append("n", number); f.searchParams.append("n", number);
console.log("fetching", f); console.log("fetching", f);
const c = await fetch(f) return await fetch(f)
.then((r) => r.json()) .then((r) => r.json())
.then((n) => { .then((n) => {
console.log(n); console.log(n);
arrlen = n.length; if (n.length > 0) {
if (n) {
const ol = document.createElement("ol"); const ol = document.createElement("ol");
for (const i of n) { for (const i of n) {
const li = document.createElement("li"); const li = document.createElement("li");
@@ -64,7 +58,7 @@
ol.appendChild(li); ol.appendChild(li);
} }
bij.textContent = `next ${ bij.textContent = `next ${
n.length != 1 n.length > 1
? `${n.length} entries are` ? `${n.length} entries are`
: "entry is" : "entry is"
} SO gone`; } SO gone`;
@@ -72,26 +66,31 @@
} else { } else {
bij.textContent = "wym empty"; bij.textContent = "wym empty";
} }
return n.length;
}) })
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
arrlen = 0;
bij.textContent = `shit. cant fetch`; bij.textContent = `shit. cant fetch`;
return 0;
}); });
}; };
n.addEventListener("change", changeHandler); n.addEventListener("change", changeHandler);
/** @type {HTMLFormElement} */ /** @type {HTMLFormElement} */
const c = document.getElementById("c"); const c = document.getElementById("c");
c.addEventListener("submit", async (e) => { c.addEventListener("submit", async (e) => {
e.preventDefault(); e.preventDefault();
await changeHandler(); await changeHandler();
}); });
changeHandler(); changeHandler();
const s = document.getElementById("s"); const s = document.getElementById("s");
s.addEventListener("click", (e) => { s.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();
console.log("ts runs"); console.log("ts runs");
changeHandler(); const arrlen = changeHandler();
for (const _ of Array(arrlen)) { for (const _ of Array(arrlen)) {
const w = window.open( const w = window.open(
"/r", "/r",
@@ -103,11 +102,12 @@
arrlen = 0; arrlen = 0;
bij.textContent = "something idk"; bij.textContent = "something idk";
}); });
/** @type {HTMLButtonElement} */ /** @type {HTMLButtonElement} */
const all = document.getElementById("all"); const all = document.getElementById("all");
all.addEventListener( all.addEventListener(
"click", "click",
async () => async (e) =>
await fetch("/api/queue/len") await fetch("/api/queue/len")
.then((d) => d.json()) .then((d) => d.json())
.then((len) => { .then((len) => {
@@ -120,7 +120,6 @@
); );
console.log(w); console.log(w);
} }
arrlen = 0;
bij.textContent = "everything idk"; bij.textContent = "everything idk";
}) })
.catch((e) => console.error(e)) .catch((e) => console.error(e))
+20 -18
View File
@@ -4,32 +4,34 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Queue Input</title> <title>Queue Input</title>
<link
rel="stylesheet"
href="{{ url_for('static', path='style.css') }}"
/>
</head> </head>
<body> <body>
<div style="display: flex; align-items: center; justify-content: space-between; width: 100%; max-width: 800px; margin-bottom: 20px;">
<h1 style="margin: 0;">Add Links</h1>
<a href="/" class="btn" style="margin: 0;">&larr; Home</a>
</div>
<form method="POST" action="/queuein"> <form method="POST" action="/queuein">
<label for="lines">in:</label> <textarea id="lines" rows="20" cols="100" name="lines"></textarea>
<textarea
id="l"
rows="20"
cols="100"
style="min-width: 20%; min-height: 20dvh"
name="lines"
></textarea>
<input type="submit" value="ahh" /> <input type="submit" value="ahh" />
</form> </form>
<script> <script>
// lifted from google search ai
/**@type {HTMLTextAreaElement} */ /**@type {HTMLTextAreaElement} */
( (document.getElementById("lines")).addEventListener(
// lifted from google search ai "keydown",
document.getElementById("l") function (e) {
).addEventListener("keydown", function (e) { // Check for Ctrl+Enter (Windows/Linux) or Command+Enter (Mac)
// Check for Ctrl+Enter (Windows/Linux) or Command+Enter (Mac) if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { e.preventDefault(); // Prevent the default newline character
e.preventDefault(); // Prevent the default newline character // Submit the form associated with this textarea
// Submit the form associated with this textarea this.form.submit();
this.form.submit(); }
} }
}); );
</script> </script>
{% from "macros.html" import qrcode_queue %} {% if queue_items %} {% from "macros.html" import qrcode_queue %} {% if queue_items %}
<p>total: {{ queue_items|length }}</p> <p>total: {{ queue_items|length }}</p>
+58
View File
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Queue Reader</title>
<link
rel="stylesheet"
href="{{ url_for('static', path='style.css') }}"
/>
</head>
<body>
{% from 'macros.html' import magic_link %} {% if current_link %}
<div class="card">
<h2>Current Link</h2>
<p>{{ magic_link(current_link, current_link, "a") }}</p>
<div class="qrcode" id="current-qr"></div>
<div class="actions" style="margin-top: 20px">
<button id="b" class="btn">Skip / Next</button>
</div>
<p style="margin-top: 10px; font-size: 0.8rem; color: #666">
{{ queue_length }} items remaining
</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/qrcode.js"></script>
<script>
// QR Code generation
new QRCode(document.getElementById("current-qr"), {
text: "{{ current_link }}",
correctLevel: QRCode.CorrectLevel.L,
width: 256,
height: 256,
});
// Skip button logic
document.getElementById("b").addEventListener("click", async () => {
await fetch("/r", { redirect: "manual" });
setTimeout(() => {
window.location.reload();
});
});
</script>
{% else %}
<div class="card">
<h2>Queue Empty</h2>
<p>No links in the queue. Go add some!</p>
<div class="actions">
<a href="/" class="btn">Home</a>
<a href="/queuein" class="btn">Add Links</a>
<a href="javascript:location.reload()" class="btn">Reload</a>
</div>
</div>
{% endif %}
</body>
</html>
+69
View File
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Trail</title>
<link
rel="stylesheet"
href="{{ url_for('static', path='style.css') }}"
/>
</head>
<body>
{% if queue_length > 0 %}
<div class="trail-container">
<div class="trail-count">{{ queue_length }}</div>
<p>Items Left</p>
<p
style="
max-width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
"
>
Next:
<a
href="{{ current_link }}"
target="_blank"
rel="noreferrer noopener"
>{{ current_link }}</a
>
</p>
<div class="trail-hint">Tap anywhere to open & next</div>
</div>
<script>
if (window.opener) {
// new tab = window open no "noopener". this probs does exist
console.log(/** @type {Window} */ (window.opener)); // hi hello hi
}
window.addEventListener("load", () => {
document.body.addEventListener("click", async (e) => {
if (e.target.tagName === "A") return;
// do trix
await fetch("/r", { redirect: "manual" });
// now this is the new updated
const newTab = window.open(window.location.href, "_blank");
// yo this is good no security error
window.location.replace("{{ current_link }}");
});
});
</script>
{% else %}
<div class="card">
<h2>All Done!</h2>
<p>The queue is empty.</p>
<div class="actions">
<a href="/" class="btn">Home</a>
<a href="/queuein" class="btn">Add More</a>
</div>
</div>
{% endif %}
</body>
</html>
+5 -3
View File
@@ -1,3 +1,5 @@
"why outdated"
from collections import deque from collections import deque
import flask import flask
@@ -13,11 +15,11 @@ a = flask.Flask(__name__)
queue: deque[str] = deque() queue: deque[str] = deque()
@a.route("/") @a.route("/q")
def no(): def no():
"if i click the link then it proceed. if i reload it doesnt." "if i click the link then it proceed. if i reload it doesnt."
current_link = queue[0] if queue else None current_link = queue[0] if queue else None
return flask.render_template("index.html", current_link=current_link) return flask.render_template("reader.html", current_link=current_link)
@a.route("/r") @a.route("/r")
@@ -26,7 +28,7 @@ def redir():
if queue: if queue:
q = queue.popleft() q = queue.popleft()
return flask.redirect(q) return flask.redirect(q)
return flask.render_template("index.html", current_link=None) return flask.render_template("reader.html", current_link=None)
@a.route("/queuein", methods=["GET", "POST"]) @a.route("/queuein", methods=["GET", "POST"])
+32 -8
View File
@@ -6,11 +6,13 @@ from itertools import islice
import uvicorn import uvicorn
from fastapi import FastAPI, Form, Request from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
app = FastAPI() app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
queue: deque[str] = deque() # lifo ahh queue: deque[str] = deque() # fifo ahh
def link_extract_from_onetab(line): def link_extract_from_onetab(line):
@@ -20,10 +22,28 @@ def link_extract_from_onetab(line):
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def index(request: Request):
"idgaf"
return templates.TemplateResponse(request, "index.html", {"routes": app.routes})
@app.get("/q", response_class=HTMLResponse)
async def no(request: Request): async def no(request: Request):
current_link = queue[0] if queue else None current_link = queue[0] if queue else None
return templates.TemplateResponse( return templates.TemplateResponse(
request, "index.html", {"current_link": current_link} request,
"reader.html",
{"current_link": current_link, "queue_length": len(queue)},
)
@app.get("/t", response_class=HTMLResponse)
async def trail(request: Request):
current_link = queue[0] if queue else None
return templates.TemplateResponse(
request,
"trail.html",
{"current_link": current_link, "queue_length": len(queue)},
) )
@@ -32,22 +52,26 @@ async def redir(request: Request):
if queue: if queue:
q = queue.popleft() q = queue.popleft()
return RedirectResponse(url=q) return RedirectResponse(url=q)
return templates.TemplateResponse(request, "index.html", {"current_link": None}) return templates.TemplateResponse(
request, "reader.html", {"current_link": None, "queue_length": len(queue)}
)
@app.get("/m", response_class=HTMLResponse) @app.get("/m", response_class=HTMLResponse)
async def mlti(request: Request): async def mlti(request: Request):
"request first new style" """request first new style.
js still breaking (on mobile. they dont like mass popups) im so sad
"""
return templates.TemplateResponse(request, "multi.html") return templates.TemplateResponse(request, "multi.html")
@app.get("/api/queue") @app.get("/api/queue")
async def queueview(n: int | None = None, s: int | None = None): async def queue_view(n: int | None = None, s: int | None = None):
return list(islice(queue, s, n)) return list(islice(queue, s, n))
@app.get("/api/queue/len") @app.get("/api/queue/len")
async def queuelen(): async def queue_len():
return len(queue) return len(queue)
@@ -57,14 +81,14 @@ async def qrcodequeue(request: Request):
@app.get("/queuein", response_class=HTMLResponse) @app.get("/queuein", response_class=HTMLResponse)
async def cuh_get(request: Request): async def queue_in(request: Request):
return templates.TemplateResponse( return templates.TemplateResponse(
request, "queue.html", {"queue_items": list(queue)} request, "queue.html", {"queue_items": list(queue)}
) )
@app.post("/queuein") @app.post("/queuein")
async def cuh_post(lines: str = Form("")): async def send_queue(lines: str = Form("")):
"""var name == form ele name (name="lines")""" """var name == form ele name (name="lines")"""
queue.extend( queue.extend(
link_extract_from_onetab(line) for line in lines.splitlines() if line.strip() link_extract_from_onetab(line) for line in lines.splitlines() if line.strip()