Upgrade mobile UX to Oasis 0.9.0
Rebase onto upstream 0.9.0. Mobile topbar (logo->home, avatar->profile), hexagon category nav (hive) and bottom bar; remove the six top-bar buttons (inbox/pm/publish/search/graphos/peers) that cluttered the header. Theme OasisMobileUX carries topbar/hexagon/bottombar styles.
This commit is contained in:
parent
de36b38851
commit
8d21216362
69 changed files with 8974 additions and 820 deletions
|
|
@ -82,7 +82,7 @@ const HIDDEN_ENVELOPE_TYPES = new Set([
|
|||
'courts-key'
|
||||
]);
|
||||
|
||||
module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel }) => {
|
||||
module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel, industryModel }) => {
|
||||
let ssb;
|
||||
const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb };
|
||||
|
||||
|
|
@ -143,6 +143,72 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel }) => {
|
|||
return t.length > max ? t.slice(0, max - 1) + '…' : t;
|
||||
};
|
||||
|
||||
// Builds self-contained chatThread items (title + recent messages embedded) for
|
||||
// general (non-tribe) OPEN chats that have activity. Emitted as one item per chat
|
||||
// with ts = latest message, so it survives the recent/all/mine filters intact.
|
||||
const CHAT_THREAD_LIMIT = 6;
|
||||
const buildChatThreads = async (ssbClient, idToAction, rootOf) => {
|
||||
const replacedChatIds = new Set();
|
||||
for (const a of idToAction.values()) {
|
||||
if (a.type !== 'chat') continue;
|
||||
const rep = a.content && a.content.replaces;
|
||||
if (rep) replacedChatIds.add(rep);
|
||||
}
|
||||
const stateByRoot = new Map();
|
||||
for (const a of idToAction.values()) {
|
||||
if (a.type !== 'chat') continue;
|
||||
const root = rootOf(a.id);
|
||||
const isTip = !replacedChatIds.has(a.id);
|
||||
const prev = stateByRoot.get(root);
|
||||
// Resolve the chain tip by replaces (a non-replaced action wins), breaking ts
|
||||
// ties correctly — the mock and real SSB can stamp equal timestamps.
|
||||
if (prev && !((isTip && !prev.isTip) || (isTip === prev.isTip && (a.ts || 0) >= prev.ts))) continue;
|
||||
const cc = a.content || {};
|
||||
stateByRoot.set(root, { ts: a.ts || 0, isTip, status: String(cc.status || '').toUpperCase(), tribeId: cc.tribeId || null, title: String(cc.title || ''), description: String(cc.description || '') });
|
||||
}
|
||||
const msgsByRoot = new Map();
|
||||
for (const a of idToAction.values()) {
|
||||
if (a.type !== 'chatMessage') continue;
|
||||
const c = a.content || {};
|
||||
if (c.tribeId || c.encryptedText) continue;
|
||||
const root = c.chatId;
|
||||
if (!root || typeof c.text !== 'string' || !c.text) continue;
|
||||
if (!msgsByRoot.has(root)) msgsByRoot.set(root, []);
|
||||
msgsByRoot.get(root).push(a);
|
||||
}
|
||||
for (const root of msgsByRoot.keys()) {
|
||||
if (stateByRoot.has(root)) continue;
|
||||
const val = await getMsg(ssbClient, root);
|
||||
const cc = val && val.content;
|
||||
if (cc && typeof cc === 'object' && cc.type === 'chat') {
|
||||
stateByRoot.set(root, { ts: 0, status: String(cc.status || '').toUpperCase(), tribeId: cc.tribeId || null, title: String(cc.title || ''), description: String(cc.description || '') });
|
||||
} else {
|
||||
stateByRoot.set(root, null);
|
||||
}
|
||||
}
|
||||
const items = [];
|
||||
for (const [root, msgs] of msgsByRoot.entries()) {
|
||||
const info = stateByRoot.get(root);
|
||||
if (!info || info.tribeId || info.status !== 'OPEN') continue;
|
||||
const asc = msgs.slice().sort((x, y) => (x.ts || 0) - (y.ts || 0));
|
||||
const last = asc[asc.length - 1];
|
||||
items.push({
|
||||
id: `chatthread:${root}`,
|
||||
type: 'chatThread',
|
||||
author: last.author,
|
||||
ts: last.ts || 0,
|
||||
content: {
|
||||
type: 'chatThread',
|
||||
chatRoot: root,
|
||||
title: info.title || '',
|
||||
description: info.description || '',
|
||||
replies: asc.slice(-CHAT_THREAD_LIMIT).map(m => ({ id: m.id, author: m.author, ts: m.ts || 0, text: (m.content && m.content.text) || '' }))
|
||||
}
|
||||
});
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
return {
|
||||
invalidateCache() {
|
||||
_feedCache = null;
|
||||
|
|
@ -256,7 +322,7 @@ module.exports = ({ cooler, tribeCrypto, tribesModel, padsModel }) => {
|
|||
const target = String(c.target || '');
|
||||
const author = v.author;
|
||||
if (!target || !author) continue;
|
||||
const key = `${target} | ||||