Files
tatuchat/assets/miniapps/tasks.html
Welton Moura 1f692b1e4e
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
feat: mini apps customizados com importacao por arquivo/URL, paleta de cores, avatar autenticado e documentacao
2026-09-18 15:11:12 -03:00

209 lines
5.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Tasks</title>
<style>
:root {
--accent: #00D084;
--bg: #ffffff;
--fg: #1a1a1a;
--card: #f6f6f6;
--border: #e3e3e3;
--muted: #757575;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--fg: #e3e3e3;
--card: #1e1e1e;
--border: #333333;
--muted: #9e9e9e;
}
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--fg);
min-height: 100vh;
}
main { max-width: 640px; margin: 0 auto; padding: 20px 16px 32px; }
header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
header .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }
header h1 { font-size: 1.3rem; font-weight: 700; }
#addForm { display: flex; gap: 8px; margin-bottom: 20px; }
#taskInput {
flex: 1;
padding: 11px 14px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--card);
color: var(--fg);
font-size: 0.95rem;
outline: none;
}
#taskInput:focus { border-color: var(--accent); }
#addBtn {
width: 44px;
border: none;
border-radius: 10px;
background: var(--accent);
color: #fff;
font-size: 1.4rem;
line-height: 1;
cursor: pointer;
}
ul { list-style: none; }
li {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 10px;
margin-bottom: 10px;
background: var(--card);
}
li.done span { text-decoration: line-through; opacity: .55; }
li input[type="checkbox"] {
flex-shrink: 0;
width: 18px;
height: 18px;
accent-color: var(--accent);
cursor: pointer;
}
li span { flex: 1; word-break: break-word; }
li button {
flex-shrink: 0;
background: none;
border: none;
color: var(--muted);
font-size: 0.9rem;
cursor: pointer;
padding: 4px;
}
li button:hover { color: var(--fg); }
.empty { text-align: center; padding: 48px 0 24px; color: var(--muted); }
</style>
</head>
<body>
<main>
<header>
<div class="dot"></div>
<h1>Tasks</h1>
</header>
<form id="addForm" autocomplete="off">
<input id="taskInput" placeholder="What do you need to do?" />
<button id="addBtn" type="submit">+</button>
</form>
<ul id="list"></ul>
</main>
<script>
const APP_ID = 'tasks';
// ----- Bridge: the app talks to the TatuChat client through postMessage.
// The host replies with {api, widgetId, requestId, response:{data,error}}.
const _pending = new Map();
let _requestCounter = 0;
function bridgeRequest(action, data) {
return new Promise(function (resolve, reject) {
const requestId = 'r' + (++_requestCounter);
_pending.set(requestId, { resolve: resolve, reject: reject });
window.parent.postMessage({
api: 'fromMiniApp',
widgetId: APP_ID,
requestId: requestId,
action: action,
data: data
}, '*');
});
}
window.addEventListener('message', function (event) {
const msg = event.data;
if (!msg || msg.api !== 'fromMiniApp' || !msg.requestId) return;
if (!_pending.has(msg.requestId)) return;
const pending = _pending.get(msg.requestId);
_pending.delete(msg.requestId);
const response = msg.response || {};
if (response.error) {
pending.reject(new Error(response.error));
} else {
pending.resolve(response.data);
}
});
// ----- Tasks application logic.
let tasks = [];
const listEl = document.getElementById('list');
const addForm = document.getElementById('addForm');
const taskInput = document.getElementById('taskInput');
addForm.addEventListener('submit', function (event) {
event.preventDefault();
const text = taskInput.value.trim();
if (!text) return;
tasks.push({ id: String(Date.now()), text: text, done: false });
taskInput.value = '';
render();
save();
});
function toggleTask(id) {
const task = tasks.find(function (t) { return t.id === id; });
if (!task) return;
task.done = !task.done;
render();
save();
}
function deleteTask(id) {
tasks = tasks.filter(function (t) { return t.id !== id; });
render();
save();
}
function render() {
listEl.textContent = '';
if (tasks.length === 0) {
const empty = document.createElement('div');
empty.className = 'empty';
empty.textContent = 'No tasks yet';
listEl.appendChild(empty);
return;
}
tasks.forEach(function (task) {
const li = document.createElement('li');
if (task.done) li.className = 'done';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = task.done;
checkbox.addEventListener('change', function () { toggleTask(task.id); });
const span = document.createElement('span');
span.textContent = task.text;
const del = document.createElement('button');
del.textContent = '\u2715';
del.setAttribute('aria-label', 'Delete task');
del.addEventListener('click', function () { deleteTask(task.id); });
li.append(checkbox, span, del);
listEl.appendChild(li);
});
}
function load() {
return bridgeRequest('io.tatuchat.storage.load').then(function (data) {
if (data && Array.isArray(data.tasks)) tasks = data.tasks;
}).catch(function () {});
}
function save() {
return bridgeRequest('io.tatuchat.storage.save', { tasks: tasks }).catch(function () {});
}
load().finally(render);
</script>
</body>
</html>