[0.9.1] FORK_IA_UX v2: fix top-bar (visibility sheet) + Karvan module (ephemeral messages + WebRTC)
Navigation fixes: - The bottom "Explore" sheet no longer blocks the top bar: closed state is visibility:hidden (out of hit-testing), so the top identity buttons and page content always receive taps (fixes "top buttons don't work"). - Top bar is now identity only (logo, avatar). The Personal/Community filter moved into the Explore sheet, next to the hive, as a segmented control. New module "Karvan" (self-contained; inspired by karvan-protocol ephemeral rooms): - Ephemeral/temporary chat rooms held only in RAM, self-destructing on idle (30 min) / absolute (2 h) TTL — nothing is written to disk. - Server relay chat (RAM + polling) as the reliable path, plus a WebRTC data-channel layer (perfect negotiation, HTTP signaling mailbox) for instant P2P delivery; degrades to the relay if WebRTC is unavailable. Data-channel only (no camera/mic) so no wrapper permission changes are needed. - Files: models/karvan_model.js, views/karvan_view.js, client/public/js/karvan.js; routes /karvan* + karvanMod config + nav entry (network) + karvanTitle i18n (11 langs). Additive and gated by OASIS_MOBILE / karvanMod; Linux and the UX_OASIS branch untouched.
This commit is contained in:
parent
745c340917
commit
e938c8bb42
19 changed files with 592 additions and 8 deletions
91
src/views/karvan_view.js
Normal file
91
src/views/karvan_view.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"use strict";
|
||||
// Vista del módulo KARVAN (mensajes temporales + WebRTC). El chat va por relay servidor (RAM,
|
||||
// autodestrucción) y, si conectan pares, por data-channel WebRTC (instantáneo). El <script> solo
|
||||
// se carga en la página de sala; el WebRTC usa data-channel (sin cámara/mic => sin permisos extra).
|
||||
|
||||
const { div, section, h2, h3, p, a, form, input, button, span, ul, li, script, label } =
|
||||
require("../server/node_modules/hyperaxe");
|
||||
const { template, i18n } = require("./main_views");
|
||||
|
||||
const fmtExpires = (expiresAt) => {
|
||||
const mins = Math.max(0, Math.round((expiresAt - Date.now()) / 60000));
|
||||
if (mins >= 60) return `${Math.floor(mins / 60)}h ${mins % 60}m`;
|
||||
return `${mins}m`;
|
||||
};
|
||||
|
||||
const shortId = (id) => {
|
||||
id = String(id || "?");
|
||||
return id.startsWith("@") ? id.slice(1, 7) : id.slice(0, 6);
|
||||
};
|
||||
exports.karvanShortId = shortId;
|
||||
|
||||
const renderMsg = (m) =>
|
||||
li({ class: "karvan-msg", "data-seq": String(m.seq), "data-mid": m.mid || "" },
|
||||
span({ class: "km-from" }, shortId(m.from)),
|
||||
span({ class: "km-text" }, m.text)
|
||||
);
|
||||
|
||||
const ttlOpt = (val, lbl, checked) =>
|
||||
label({ class: "karvan-ttl-opt" },
|
||||
input(Object.assign({ type: "radio", name: "ttl", value: val }, checked ? { checked: "checked" } : {})),
|
||||
span(lbl)
|
||||
);
|
||||
|
||||
exports.karvanView = (rooms, params = {}) =>
|
||||
template(
|
||||
i18n.karvanTitle || "Karvan",
|
||||
section({ class: "karvan-intro" },
|
||||
h2("Karvan · " + (i18n.karvanEphemeral || "Ephemeral rooms")),
|
||||
p({ class: "karvan-sub" },
|
||||
i18n.karvanBlurb ||
|
||||
"Temporary rooms that self-destruct. Messages live only in memory — never saved — and vanish when the room expires. Real-time over WebRTC when a peer connects.")
|
||||
),
|
||||
section({ class: "karvan-create" },
|
||||
form({ method: "POST", action: "/karvan/create" },
|
||||
input({ type: "text", name: "title", placeholder: i18n.karvanRoomName || "Room name (optional)", maxlength: "80", autocomplete: "off" }),
|
||||
div({ class: "karvan-ttl" },
|
||||
span({ class: "karvan-ttl-lbl" }, i18n.karvanTtl || "Self-destruct in:"),
|
||||
ttlOpt("30", "30m", true), ttlOpt("120", "2h", false), ttlOpt("480", "8h", false)
|
||||
),
|
||||
button({ type: "submit" }, i18n.karvanCreate || "Create ephemeral room")
|
||||
)
|
||||
),
|
||||
section({ class: "karvan-list" },
|
||||
h3(i18n.karvanActive || "Active rooms"),
|
||||
rooms && rooms.length
|
||||
? ul({ class: "karvan-rooms" },
|
||||
...rooms.map((r) =>
|
||||
li(
|
||||
a({ href: "/karvan/" + r.id, class: "karvan-room-item" },
|
||||
span({ class: "kr-title" }, r.title || "room " + r.id.slice(0, 6)),
|
||||
span({ class: "kr-meta" }, `${r.count} · ⏳ ${fmtExpires(r.expiresAt)}`)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
: p({ class: "karvan-empty" }, i18n.karvanNone || "No active rooms. Create one — it disappears on its own.")
|
||||
)
|
||||
);
|
||||
|
||||
exports.karvanRoomView = (room, messages, params = {}) =>
|
||||
template(
|
||||
"Karvan · " + (room.title || room.id.slice(0, 6)),
|
||||
section({ class: "karvan-room", "data-room": room.id, "data-self": params.selfId || "" },
|
||||
div({ class: "karvan-room-head" },
|
||||
a({ href: "/karvan", class: "karvan-back" }, "‹ " + (i18n.karvanRooms || "Rooms")),
|
||||
span({ class: "karvan-room-title" }, room.title || "room " + room.id.slice(0, 6)),
|
||||
span({ class: "karvan-expire", id: "karvan-expire" }, "⏳ " + fmtExpires(room.expiresAt))
|
||||
),
|
||||
div({ class: "karvan-live", id: "karvan-live" },
|
||||
span({ class: "kl-dot" }),
|
||||
span({ class: "kl-txt", id: "karvan-live-txt" }, i18n.karvanRelay || "server relay")
|
||||
),
|
||||
ul({ class: "karvan-msgs", id: "karvan-msgs" }, ...(messages || []).map(renderMsg)),
|
||||
form({ class: "karvan-compose", method: "POST", action: "/karvan/" + room.id + "/msg", id: "karvan-form" },
|
||||
input({ type: "hidden", name: "from", value: params.selfId || "" }),
|
||||
input({ type: "text", name: "text", id: "karvan-text", placeholder: i18n.karvanSay || "Temporary message…", maxlength: "2000", autocomplete: "off" }),
|
||||
button({ type: "submit" }, i18n.karvanSend || "Send")
|
||||
)
|
||||
),
|
||||
script({ src: "/js/karvan.js", defer: "defer" })
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue