Compare commits

..
6 Commits
10 changed files with 497 additions and 67 deletions

No files matched your search

+2 -4
View File
@@ -1,6 +1,4 @@
def main():
print("Hello from qrcode-from-onetab!")
import uvicorn
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>
<meta charset="UTF-8" />
<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>
<body>
{% from 'macros.html' import magic_link %} {% if current_link %}
<p>{{ magic_link(current_link, current_link, "a") }}</p>
<button id="b">skip/fw</button>
<script>
document.getElementById("b").addEventListener("click", async () => {
await fetch("/r", { method: "GET", redirect: "manual" });
setTimeout(() => {
window.location.reload();
});
});
</script>
{% else %}
<p>no</p>
<button onclick="location.reload();">reload</button>
{% endif %}
<h1>Queue Manager</h1>
<div class="hero">
<div class="card">
<h2>Input (PC)</h2>
<p>
Paste your OneTab export or list of links here to add them
to the queue.
</p>
<div class="actions">
<a href="/queuein" class="btn">Add Links</a>
</div>
</div>
<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>
</html>
+3 -9
View File
@@ -24,7 +24,7 @@
})();
</script>
{% endmacro %} {% macro qrcode_queue(queue_items) %} {% if queue_items %}
<ol>
<ol class="queue-grid">
{% for item in queue_items %}
<li>
<a href="{{ item }}" target="_blank" rel="noreferrer noopener">
@@ -33,14 +33,6 @@
</a>
</li>
{% endfor %}
<style>
/* it works ig */
& {
li:has(.qrcode) {
width: min-content;
}
}
</style>
</ol>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/qrcode.js"></script>
<script>
@@ -50,6 +42,8 @@
new QRCode(e, {
text: a,
correctLevel: QRCode.CorrectLevel.L,
width: 256,
height: 256,
});
});
});
+13 -14
View File
@@ -4,11 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Queue dump type shit</title>
<style>
li {
width: min-content;
}
</style>
</head>
<body>
@@ -34,9 +29,9 @@
<script>
/** @type {HTMLDivElement} */
const bij = document.getElementById("gonk");
let arrlen = 0;
/** @type {HTMLInputElement} */
const n = document.getElementById("n");
const changeHandler = async () => {
const number = parseInt(n.value);
if (isNaN(number)) {
@@ -46,12 +41,11 @@
const f = new URL("/api/queue", window.location.href);
f.searchParams.append("n", number);
console.log("fetching", f);
const c = await fetch(f)
return await fetch(f)
.then((r) => r.json())
.then((n) => {
console.log(n);
arrlen = n.length;
if (n) {
if (n.length > 0) {
const ol = document.createElement("ol");
for (const i of n) {
const li = document.createElement("li");
@@ -64,7 +58,7 @@
ol.appendChild(li);
}
bij.textContent = `next ${
n.length != 1
n.length > 1
? `${n.length} entries are`
: "entry is"
} SO gone`;
@@ -72,26 +66,31 @@
} else {
bij.textContent = "wym empty";
}
return n.length;
})
.catch((e) => {
console.error(e);
arrlen = 0;
bij.textContent = `shit. cant fetch`;
return 0;
});
};
n.addEventListener("change", changeHandler);
/** @type {HTMLFormElement} */
const c = document.getElementById("c");
c.addEventListener("submit", async (e) => {
e.preventDefault();
await changeHandler();
});
changeHandler();
const s = document.getElementById("s");
s.addEventListener("click", (e) => {
e.preventDefault();
console.log("ts runs");
changeHandler();
const arrlen = changeHandler();
for (const _ of Array(arrlen)) {
const w = window.open(
"/r",
@@ -103,11 +102,12 @@
arrlen = 0;
bij.textContent = "something idk";
});
/** @type {HTMLButtonElement} */
const all = document.getElementById("all");
all.addEventListener(
"click",
async () =>
async (e) =>
await fetch("/api/queue/len")
.then((d) => d.json())
.then((len) => {
@@ -120,7 +120,6 @@
);
console.log(w);
}
arrlen = 0;
bij.textContent = "everything idk";
})
.catch((e) => console.error(e))
+15 -13
View File
@@ -4,32 +4,34 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Queue Input</title>
<link
rel="stylesheet"
href="{{ url_for('static', path='style.css') }}"
/>
</head>
<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">
<label for="lines">in:</label>
<textarea
id="l"
rows="20"
cols="100"
style="min-width: 20%; min-height: 20dvh"
name="lines"
></textarea>
<textarea id="lines" rows="20" cols="100" name="lines"></textarea>
<input type="submit" value="ahh" />
</form>
<script>
/**@type {HTMLTextAreaElement} */
(
// lifted from google search ai
document.getElementById("l")
).addEventListener("keydown", function (e) {
/**@type {HTMLTextAreaElement} */
(document.getElementById("lines")).addEventListener(
"keydown",
function (e) {
// Check for Ctrl+Enter (Windows/Linux) or Command+Enter (Mac)
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
e.preventDefault(); // Prevent the default newline character
// Submit the form associated with this textarea
this.form.submit();
}
});
}
);
</script>
{% from "macros.html" import qrcode_queue %} {% if queue_items %}
<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
import flask
@@ -13,11 +15,11 @@ a = flask.Flask(__name__)
queue: deque[str] = deque()
@a.route("/")
@a.route("/q")
def no():
"if i click the link then it proceed. if i reload it doesnt."
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")
@@ -26,7 +28,7 @@ def redir():
if queue:
q = queue.popleft()
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"])
+32 -8
View File
@@ -6,11 +6,13 @@ from itertools import islice
import uvicorn
from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
queue: deque[str] = deque() # lifo ahh
queue: deque[str] = deque() # fifo ahh
def link_extract_from_onetab(line):
@@ -20,10 +22,28 @@ def link_extract_from_onetab(line):
@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):
current_link = queue[0] if queue else None
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:
q = queue.popleft()
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)
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")
@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))
@app.get("/api/queue/len")
async def queuelen():
async def queue_len():
return len(queue)
@@ -57,14 +81,14 @@ async def qrcodequeue(request: Request):
@app.get("/queuein", response_class=HTMLResponse)
async def cuh_get(request: Request):
async def queue_in(request: Request):
return templates.TemplateResponse(
request, "queue.html", {"queue_items": list(queue)}
)
@app.post("/queuein")
async def cuh_post(lines: str = Form("")):
async def send_queue(lines: str = Form("")):
"""var name == form ele name (name="lines")"""
queue.extend(
link_extract_from_onetab(line) for line in lines.splitlines() if line.strip()