diff --git a/android/tinySSB/app/src/main/assets/web/prod/chat.js b/android/tinySSB/app/src/main/assets/web/prod/chat.js new file mode 100644 index 0000000..58f0430 --- /dev/null +++ b/android/tinySSB/app/src/main/assets/web/prod/chat.js @@ -0,0 +1,414 @@ +// prod/chat.js + +"use strict"; + +var curr_chat; +var curr_img_candidate = null; +// var restream = false // whether the backend is currently restreaming all posts + +// --- menu callbacks + +function menu_new_conversation() { + fill_members(); + prev_scenario = 'chats'; + setScenario("members"); + document.getElementById("div:textarea").style.display = 'none'; + document.getElementById("div:confirm-members").style.display = 'flex'; + document.getElementById("tremolaTitle").style.display = 'none'; + var c = document.getElementById("conversationTitle"); + c.style.display = null; + c.innerHTML = "Create Private Channel
Select up to 7 members"; + document.getElementById('plus').style.display = 'none'; + closeOverlay(); +} + +function menu_edit_convname() { + menu_edit('convNameTarget', "Edit conversation name:
(only you can see this name)", tremola.chats[curr_chat].alias); +} + +function menu_forget_conv() { + // toggles the forgotten flag of a conversation + if (curr_chat == recps2nm([myId])) { + launch_snackbar("cannot be applied to own notes"); + return; + } + tremola.chats[curr_chat].forgotten = !tremola.chats[curr_chat].forgotten; + persist(); + load_chat_list() // refresh list of conversations + closeOverlay(); + if (curr_scenario == 'posts' /* should always be true */ && tremola.chats[curr_chat].forgotten) + setScenario('chats'); + else + load_chat(curr_chat) // refresh currently displayed list of posts +} + +/* ?? +function menu_take_picture() { + disabled6676863(); // breakpoint using a non-existing fct,in case + closeOverlay(); + var draft = unicodeStringToTypedArray(document.getElementById('draft').value); // escapeHTML( + if (draft.length == 0) + draft = null; + else + draft = atob(draft); + console.log("getVoice" + document.getElementById('draft').value); + backend('get:voice ' + atob(draft)); +} + +function menu_pick_image() { + closeOverlay(); + backend('get:media'); +} +*/ + +// --- workflow entry points + +function new_text_post(s) { + if (s.length == 0) { + return; + } + var draft = unicodeStringToTypedArray(document.getElementById('draft').value); // escapeHTML( + var ch = tremola.chats[curr_chat] + if (!(ch.timeline instanceof Timeline)) { + ch.timeline = Timeline.fromJSON(ch.timeline) + } + let tips = JSON.stringify(ch.timeline.get_tips()) + // console.log(`tips: ${tips}`) + if (curr_chat == "ALL") { + var cmd = `publ:post ${tips} ` + btoa(draft) + " null"; // + recps + // console.log(cmd) + backend(cmd); + } else { + var recps = tremola.chats[curr_chat].members.join(' '); + var cmd = `priv:post ${tips} ` + btoa(draft) + " null " + recps; + backend(cmd); + } + document.getElementById('draft').value = ''; + closeOverlay(); + setTimeout(function () { // let image rendering (fetching size) take place before we scroll + var c = document.getElementById('core'); + c.scrollTop = c.scrollHeight; + }, 100); +} + +function new_voice_post(voice_b64) { + var draft = unicodeStringToTypedArray(document.getElementById('draft').value); // escapeHTML( + if (draft.length == 0) + draft = "null" + else + draft = btoa(draft) + var ch = tremola.chats[curr_chat] + if (!(ch.timeline instanceof Timeline)) { + ch.timeline = Timeline.fromJSON(ch.timeline) + } + let tips = JSON.stringify(ch.timeline.get_tips()) + // console.log(`tips: ${tips}`) + + if (curr_chat == "ALL") { + var cmd = `publ:post ${tips} ` + draft + " " + voice_b64; + // console.log(cmd) + backend(cmd); + } else { + var recps = tremola.chats[curr_chat].members.join(' '); + var cmd = `priv:post ${tips} ` + draft + " " + voice_b64 + " " + recps; + backend(cmd); + } + document.getElementById('draft').value = ''; +} + +function play_voice(nm, ref) { + var p = tremola.chats[nm].posts[ref]; + var d = new Date(p["when"]); + d = d.toDateString() + ' ' + d.toTimeString().substring(0, 5); + backend("play:voice " + p["voice"] + " " + btoa(fid2display(p["from"])) + " " + btoa(d)); +} + +function new_image_post() { + if (curr_img_candidate == null) { + return; + } + var draft = "![](" + curr_img_candidate + ")\n"; + var caption = document.getElementById('image-caption').value; + if (caption && caption.length > 0) + draft += caption; + var recps = tremola.chats[curr_chat].members.join(' ') + backend("priv:post " + btoa(draft) + " " + recps); + curr_img_candidate = null; + closeOverlay(); + setTimeout(function () { // let image rendering (fetching size) take place before we scroll + var c = document.getElementById('core'); + c.scrollTop = c.scrollHeight; + }, 100); +} + +function load_post_item(p) { // { 'key', 'from', 'when', 'body', 'to' (if group or public)> + var pl = document.getElementById('lst:posts'); + var is_other = p["from"] != myId; + var box = "
" + fid2display(p["from"]) + "
"; + var txt = "" + if (p["body"] != null) { + txt = escapeHTML(p["body"]).replace(/\n/g, "
\n"); + // Sketch app + if (txt.startsWith("data:image/png;base64")) { // check if the string is a data url + let compressedBase64 = txt.split(',')[1]; + // We Convert the compressed data from a base64 string to a Uint8Array + let compressedData = atob(compressedBase64) + .split('') + .map(function (char) { + return char.charCodeAt(0); + }); + let uint8Array = new Uint8Array(compressedData); + + // We to decompress the Uint8Array + let decompressedData = pako.inflate(uint8Array); + // We Convert the decompressed data back to a base64 string + let decompressedBase64 = btoa(String.fromCharCode.apply(null, decompressedData)); + // We Create a new data URL with the decompressed data + let decompressedDataURL = 'data:image/png;base64,' + decompressedBase64; + //display the data url as an image element + box += "Drawing"; + txt = ""; + } else if (txt.startsWith("data:image/svg+bipf;base64")) { + let b64 = txt.split(',')[1]; + var binStr = atob(b64) + var buf = new ArrayBuffer(binStr.length); + var ui8 = new Uint8Array(buf); + for (var i = 0; i < binStr.length; i++) + ui8[i] = binStr.charCodeAt(i); + var src; + try { + let img = bipf_decode(buf, 0); + // console.log('got svg', JSON.stringify(img)); + // img[0] -- version of this svg encoding, currently 1, ignored + if (Number.isInteger(img[0])) + img = img.slice(1) + var svg = ` { + if (e[0] == 'c') { + colNdx = e[1]; + } else if (e[0] == 'w') { + widNdx = e[1]; + } else if (e[0] == 'p') { + svg += `` + } + }) + svg += ''; + // console.log('svg:', svg) + src = `data:image/svg+xml;base64,${btoa(svg)}`; + } catch (error) { + console.error(error); + src = "data:null"; + } + box += `  "); + // txt = txt + "    (!)"; + // console.log(txt); + } + if (p.voice != null) + box += "🔊  " + box += txt + var d = new Date(p["when"]); + d = d.toDateString() + ' ' + d.toTimeString().substring(0, 5); + box += "
"; + box += d + "
"; + var row; + if (is_other) { + var c = tremola.contacts[p.from] + row = "" + // row = ">" + row += "" + box + ""; + } else { + row = "" + box; + row += "<" + } + pl.insertRow(pl.rows.length).innerHTML = row; +} + +function load_chat(nm) { + var ch, pl, e; + ch = tremola.chats[nm] + if (ch.timeline == null) + ch["timeline"] = new Timeline(); + pl = document.getElementById("lst:posts"); + while (pl.rows.length) { + pl.deleteRow(0); + } + pl.insertRow(0).innerHTML = "     "; + curr_chat = nm; + for (var n of ch.timeline.linear) { + load_post_item(ch.posts[n.name]) + } + /* + var lop = []; // list of posts + for (var p in ch.posts) lop.push(p) + lop.sort(function (a, b) { + return ch.posts[a].when - ch.posts[b].when + }) + lop.forEach(function (p) { + load_post_item(ch.posts[p]) + }) + */ + load_chat_title(ch); + setScenario("posts"); + document.getElementById("tremolaTitle").style.display = 'none'; + // update unread badge: + ch["lastRead"] = Date.now(); + persist(); + document.getElementById(nm + '-badge').style.display = 'none' // is this necessary? + /* + // scroll to bottom: + var c = document.getElementById('core'); + c.scrollTop = c.scrollHeight; + document.getElementById('lst:posts').scrollIntoView(false) + // console.log("did scroll down, but did it do it?") + */ +} + +function load_chat_title(ch) { + var c = document.getElementById("conversationTitle"), bg, box; + c.style.display = null; + c.setAttribute('classList', ch.forgotten ? 'gray' : '') // old JS (SDK 23) + box = "
" + escapeHTML(ch.alias) + "
"; + var mem = "[ALL]" + if (ch.members.length > 1 || ch.members[0] != "ALL") + mem = "🔒 " + recps2display(ch.members) + box += "
" + escapeHTML(mem) + "
"; + c.innerHTML = box; +} + +function load_chat_list() { + document.getElementById('lst:chats').innerHTML = ''; + load_chat_item("ALL") + var meOnly = recps2nm([myId]) + if (!(meOnly in tremola.chats)) { + tremola.chats[meOnly] = { + "alias": "--- local notes (for my eyes only)", "posts": {}, "forgotten": false, + "members": [myId], "touched": Date.now(), "lastRead": 0, + "timeline": new Timeline() + }; + } + load_chat_item(meOnly) + var lop = []; + for (var p in tremola.chats) { + if (p != "ALL" && p != meOnly && !tremola.chats[p]['forgotten']) + lop.push(p) + } + lop.sort(function (a, b) { + return tremola.chats[b]["touched"] - tremola.chats[a]["touched"] + }) + lop.forEach(function (p) { + load_chat_item(p) + }) + // forgotten chats: unsorted + if (!tremola.settings.hide_forgotten_conv) + for (var p in tremola.chats) + if (p != meOnly && tremola.chats[p]['forgotten']) + load_chat_item(p) +} + +function load_chat_item(nm) { // appends a button for conversation with name nm to the conv list + var cl, mem, item, bg, row, badge, badgeId, cnt; + cl = document.getElementById('lst:chats'); + // console.log(nm) + if (nm == "ALL") + mem = "ALL"; + else + mem = "🔒 " + recps2display(tremola.chats[nm].members); + item = document.createElement('div'); + // item.style = "padding: 0px 5px 10px 5px; margin: 3px 3px 6px 3px;"; + item.setAttribute('class', 'chat_item_div'); // old JS (SDK 23) + if (tremola.chats[nm].forgotten) bg = ' gray'; else bg = ' light'; + row = ""; + row += "" + item.innerHTML = row; + cl.appendChild(item); + set_chats_badge(nm) +} + +function new_conversation() { + // { "alias":"local notes (for my eyes only)", "posts":{}, "members":[myId], "touched": millis } + var recps = [] + for (var m in tremola.contacts) { + if (document.getElementById(m).checked) + recps.push(m); + } + if (recps.indexOf(myId) < 0) + recps.push(myId); + if (recps.length > 7) { + launch_snackbar("Too many recipients"); + return; + } + var cid = recps2nm(recps) + if (cid in tremola.chats) { + if (tremola.chats[cid].forgotten) { + tremola.chats[cid].forgotten = false; + load_chat_list(); // refresh + } else + launch_snackbar("Conversation already exists"); + return; + } + var nm = recps2nm(recps); + if (!(nm in tremola.chats)) { + tremola.chats[nm] = { + "alias": "Private", "posts": {}, + "members": recps, "touched": Date.now(), + "timeline": new Timeline() + }; + persist(); + } else + tremola.chats[nm]["touched"] = Date.now() + load_chat_list(); + setScenario("chats") + curr_chat = nm + menu_edit_convname() +} + +function getUnreadCnt(nm) { + var c = tremola.chats[nm], cnt = 0; + for (var p in c.posts) { + if (c.posts[p].when > c.lastRead) + cnt++; + } + return cnt; +} + +function set_chats_badge(nm) { + var e = document.getElementById(nm + '-badge'), cnt; + cnt = getUnreadCnt(nm) + if (cnt == 0) { + e.style.display = 'none'; + return + } + e.style.display = null; + if (cnt > 9) cnt = ">9"; else cnt = "" + cnt; + e.innerHTML = cnt +} + +// --- eof diff --git a/android/tinySSB/app/src/main/assets/web/prod/contacts.js b/android/tinySSB/app/src/main/assets/web/prod/contacts.js new file mode 100644 index 0000000..74ee980 --- /dev/null +++ b/android/tinySSB/app/src/main/assets/web/prod/contacts.js @@ -0,0 +1,146 @@ +// prod/contacts.js + +"use strict"; + +var new_contact_id = ''; + +// --- menu callbacks + +function menu_new_contact() { + document.getElementById('new_contact-overlay').style.display = 'initial'; + document.getElementById('overlay-bg').style.display = 'initial'; + // document.getElementById('chat_name').focus(); + overlayIsActive = true; +} + +function menu_import_id() { + closeOverlay(); + document.getElementById('import-id-overlay').style.display = 'initial' + document.getElementById('overlay-bg').style.display = 'initial' +} + +function btn_import_id() { + var str = document.getElementById('import-id-input').value + if(str == "") + return + var r = import_id(str) + if(r) { + launch_snackbar("Successfully imported, restarting...") + } else { + launch_snackbar("wrong format") + } +} + +function load_contact_list() { + document.getElementById("lst:contacts").innerHTML = ''; + for (var id in tremola.contacts) + if (!tremola.contacts[id].forgotten) + load_contact_item([id, tremola.contacts[id]]); + if (!tremola.settings.hide_forgotten_contacts) + for (var id in tremola.contacts) { + var c = tremola.contacts[id] + if (c.forgotten) + load_contact_item([id, c]); + } +} + +function load_contact_item(c) { // [ id, { "alias": "thealias", "initial": "T", "color": "#123456" } ] } + var row, item = document.createElement('div'), bg; + item.setAttribute('style', 'padding: 0px 5px 10px 5px;'); // old JS (SDK 23) + if (!("initial" in c[1])) { + c[1]["initial"] = c[1].alias.substring(0, 1).toUpperCase(); + persist(); + } + if (!("color" in c[1])) { + c[1]["color"] = colors[Math.floor(colors.length * Math.random())]; + persist(); + } + // console.log("load_c_i", JSON.stringify(c[1])) + bg = c[1].forgotten ? ' gray' : ' light'; + row = ""; + row += ""; + // var row = ""; + // console.log(row); + item.innerHTML = row; + document.getElementById('lst:contacts').appendChild(item); +} + +function show_contact_details(id) { + if (id == myId) { + document.getElementById('old_contact_alias_hdr').innerHTML = "Alias: (own name, visible to others)" + } else { + document.getElementById('old_contact_alias_hdr').innerHTML = "Alias: (only you can see this alias)" + } + var c = tremola.contacts[id]; + new_contact_id = id; + document.getElementById('old_contact_alias').value = c.alias ? c['alias'] : ""; + var details = ''; + details += '
IAM-Alias:  ' + (c.iam != "" ? c.iam : "—") + '
\n'; + details += '
Shortname:  ' + id2b32(id) + '
\n'; + details += '
SSB identity:  ' + id + '
\n'; + details += '
Forget this contact
' + document.getElementById('old_contact_details').innerHTML = details; + document.getElementById('old_contact-overlay').style.display = 'initial'; + document.getElementById('overlay-bg').style.display = 'initial'; + document.getElementById('hide_contact').checked = c.forgotten; + + document.getElementById('old_contact_alias').focus(); + overlayIsActive = true; +} + +function toggle_forget_contact(e) { + var c = tremola.contacts[new_contact_id]; + c.forgotten = !c.forgotten; + persist(); + closeOverlay(); + load_contact_list(); +} + +function save_content_alias() { + var c = tremola.contacts[new_contact_id]; + var val = document.getElementById('old_contact_alias').value; + var deleteAlias = false + + val.trim() + + if (val == '') { + deleteAlias = true + if (c.iam != "" && new_contact_id != myId) { + val = c.iam + } else { + val = id2b32(new_contact_id); + } + } + var old_alias = c.alias + c.alias = val; + c.initial = val.substring(0, 1).toUpperCase(); + c.color = colors[Math.floor(colors.length * Math.random())]; + + // update names in connected devices menu + for (var l in localPeers) { + if (localPeers[l].alias == old_alias) { + localPeers[l].alias = val + refresh_connection_entry(l) + } + } + + // share new alias with others via IAM message + if(new_contact_id == myId) { + if(deleteAlias) { + backend("iam " + btoa("")) + c.iam = "" + } else { + backend("iam " + btoa(val)) + c.iam = val + } + } + + persist(); + menu_redraw(); + closeOverlay(); +} + +// --- eof diff --git a/android/tinySSB/app/src/main/assets/web/tremola_settings.js b/android/tinySSB/app/src/main/assets/web/prod/settings.js similarity index 99% rename from android/tinySSB/app/src/main/assets/web/tremola_settings.js rename to android/tinySSB/app/src/main/assets/web/prod/settings.js index 922ec3d..ace2332 100644 --- a/android/tinySSB/app/src/main/assets/web/tremola_settings.js +++ b/android/tinySSB/app/src/main/assets/web/prod/settings.js @@ -1,8 +1,7 @@ -// tremola_settings.js +// prod/settings.js "use strict"; - // These default settings are only used for browser-only testing // Normally, these settings below WILL BE IGNORED and loaded via the provided backend. const BrowserOnlySettings = { diff --git a/android/tinySSB/app/src/main/assets/web/tremola.html b/android/tinySSB/app/src/main/assets/web/tremola.html index e3ec280..5cd26f7 100644 --- a/android/tinySSB/app/src/main/assets/web/tremola.html +++ b/android/tinySSB/app/src/main/assets/web/tremola.html @@ -6,8 +6,11 @@ - + + + + @@ -17,7 +20,6 @@ -
diff --git a/android/tinySSB/app/src/main/assets/web/tremola.js b/android/tinySSB/app/src/main/assets/web/tremola.js index 9657b8a..79cbafe 100644 --- a/android/tinySSB/app/src/main/assets/web/tremola.js +++ b/android/tinySSB/app/src/main/assets/web/tremola.js @@ -3,22 +3,17 @@ "use strict"; var tremola; -var curr_chat; -var qr; var myId; var localPeers = {}; // feedID ~ [isOnline, isConnected] - TF, TT, FT - FF means to remove this entry var must_redraw = false; var edit_target = ''; -var new_contact_id = ''; var colors = ["#d9ceb2", "#99b2b7", "#e6cba5", "#ede3b4", "#8b9e9b", "#bd7578", "#edc951", "#ffd573", "#c2a34f", "#fbb829", "#ffab03", "#7ab317", "#a0c55f", "#8ca315", "#5191c1", "#6493a7", "#bddb88"] -var curr_img_candidate = null; var pubs = [] -var wants = {} +// var wants = {} var loaded_settings = {} // the settings provided bz the backend, will overwrite tremola.settings after initialization -var restream = false // whether the backend is currently restreaming all posts // --- menu callbacks @@ -37,27 +32,6 @@ function menu_sync() { } */ -function menu_new_conversation() { - fill_members(); - prev_scenario = 'chats'; - setScenario("members"); - document.getElementById("div:textarea").style.display = 'none'; - document.getElementById("div:confirm-members").style.display = 'flex'; - document.getElementById("tremolaTitle").style.display = 'none'; - var c = document.getElementById("conversationTitle"); - c.style.display = null; - c.innerHTML = "Create Private Channel
Select up to 7 members"; - document.getElementById('plus').style.display = 'none'; - closeOverlay(); -} - -function menu_new_contact() { - document.getElementById('new_contact-overlay').style.display = 'initial'; - document.getElementById('overlay-bg').style.display = 'initial'; - // document.getElementById('chat_name').focus(); - overlayIsActive = true; -} - function menu_new_pub() { menu_edit('new_pub_target', "Enter address of trustworthy pub

Format:
net:IP_ADDR:PORT~shs:ID_OF_PUB", ""); } @@ -106,10 +80,6 @@ function onEnter(ev) { } } -function menu_edit_convname() { - menu_edit('convNameTarget', "Edit conversation name:
(only you can see this name)", tremola.chats[curr_chat].alias); -} - // function menu_edit_new_contact_alias() { // menu_edit('new_contact_alias', "Assign alias to new contact:", ""); // } @@ -223,44 +193,12 @@ function members_confirmed() { } } -function menu_forget_conv() { - // toggles the forgotten flag of a conversation - if (curr_chat == recps2nm([myId])) { - launch_snackbar("cannot be applied to own notes"); - return; - } - tremola.chats[curr_chat].forgotten = !tremola.chats[curr_chat].forgotten; - persist(); - load_chat_list() // refresh list of conversations - closeOverlay(); - if (curr_scenario == 'posts' /* should always be true */ && tremola.chats[curr_chat].forgotten) - setScenario('chats'); - else - load_chat(curr_chat) // refresh currently displayed list of posts -} - -function menu_import_id() { - closeOverlay(); - document.getElementById('import-id-overlay').style.display = 'initial' - document.getElementById('overlay-bg').style.display = 'initial' -} - -function btn_import_id() { - var str = document.getElementById('import-id-input').value - if(str == "") - return - var r = import_id(str) - if(r) { - launch_snackbar("Successfully imported, restarting...") - } else { - launch_snackbar("wrong format") - } -} - +/* function menu_process_msgs() { backend('process.msg'); closeOverlay(); } +*/ function menu_add_pub() { // ... @@ -272,351 +210,6 @@ function menu_dump() { closeOverlay(); } -function menu_take_picture() { - disabled6676863(); // breakpoint using a non-existing fct,in case - closeOverlay(); - var draft = unicodeStringToTypedArray(document.getElementById('draft').value); // escapeHTML( - if (draft.length == 0) - draft = null; - else - draft = atob(draft); - console.log("getVoice" + document.getElementById('draft').value); - backend('get:voice ' + atob(draft)); -} - -function menu_pick_image() { - closeOverlay(); - backend('get:media'); -} - -// --- - -function new_text_post(s) { - if (s.length == 0) { - return; - } - var draft = unicodeStringToTypedArray(document.getElementById('draft').value); // escapeHTML( - var ch = tremola.chats[curr_chat] - if (!(ch.timeline instanceof Timeline)) { - ch.timeline = Timeline.fromJSON(ch.timeline) - } - let tips = JSON.stringify(ch.timeline.get_tips()) - // console.log(`tips: ${tips}`) - if (curr_chat == "ALL") { - var cmd = `publ:post ${tips} ` + btoa(draft) + " null"; // + recps - // console.log(cmd) - backend(cmd); - } else { - var recps = tremola.chats[curr_chat].members.join(' '); - var cmd = `priv:post ${tips} ` + btoa(draft) + " null " + recps; - backend(cmd); - } - document.getElementById('draft').value = ''; - closeOverlay(); - setTimeout(function () { // let image rendering (fetching size) take place before we scroll - var c = document.getElementById('core'); - c.scrollTop = c.scrollHeight; - }, 100); -} - -function new_voice_post(voice_b64) { - var draft = unicodeStringToTypedArray(document.getElementById('draft').value); // escapeHTML( - if (draft.length == 0) - draft = "null" - else - draft = btoa(draft) - var ch = tremola.chats[curr_chat] - if (!(ch.timeline instanceof Timeline)) { - ch.timeline = Timeline.fromJSON(ch.timeline) - } - let tips = JSON.stringify(ch.timeline.get_tips()) - // console.log(`tips: ${tips}`) - - if (curr_chat == "ALL") { - var cmd = `publ:post ${tips} ` + draft + " " + voice_b64; - // console.log(cmd) - backend(cmd); - } else { - var recps = tremola.chats[curr_chat].members.join(' '); - var cmd = `priv:post ${tips} ` + draft + " " + voice_b64 + " " + recps; - backend(cmd); - } - document.getElementById('draft').value = ''; -} - -function play_voice(nm, ref) { - var p = tremola.chats[nm].posts[ref]; - var d = new Date(p["when"]); - d = d.toDateString() + ' ' + d.toTimeString().substring(0, 5); - backend("play:voice " + p["voice"] + " " + btoa(fid2display(p["from"])) + " " + btoa(d)); -} - -function new_image_post() { - if (curr_img_candidate == null) { - return; - } - var draft = "![](" + curr_img_candidate + ")\n"; - var caption = document.getElementById('image-caption').value; - if (caption && caption.length > 0) - draft += caption; - var recps = tremola.chats[curr_chat].members.join(' ') - backend("priv:post " + btoa(draft) + " " + recps); - curr_img_candidate = null; - closeOverlay(); - setTimeout(function () { // let image rendering (fetching size) take place before we scroll - var c = document.getElementById('core'); - c.scrollTop = c.scrollHeight; - }, 100); -} - -function load_post_item(p) { // { 'key', 'from', 'when', 'body', 'to' (if group or public)> - var pl = document.getElementById('lst:posts'); - var is_other = p["from"] != myId; - var box = "
" + fid2display(p["from"]) + "
"; - var txt = "" - if (p["body"] != null) { - txt = escapeHTML(p["body"]).replace(/\n/g, "
\n"); - // Sketch app - if (txt.startsWith("data:image/png;base64")) { // check if the string is a data url - let compressedBase64 = txt.split(',')[1]; - // We Convert the compressed data from a base64 string to a Uint8Array - let compressedData = atob(compressedBase64) - .split('') - .map(function (char) { - return char.charCodeAt(0); - }); - let uint8Array = new Uint8Array(compressedData); - - // We to decompress the Uint8Array - let decompressedData = pako.inflate(uint8Array); - // We Convert the decompressed data back to a base64 string - let decompressedBase64 = btoa(String.fromCharCode.apply(null, decompressedData)); - // We Create a new data URL with the decompressed data - let decompressedDataURL = 'data:image/png;base64,' + decompressedBase64; - //display the data url as an image element - box += "Drawing"; - txt = ""; - } else if (txt.startsWith("data:image/svg+bipf;base64")) { - let b64 = txt.split(',')[1]; - var binStr = atob(b64) - var buf = new ArrayBuffer(binStr.length); - var ui8 = new Uint8Array(buf); - for (var i = 0; i < binStr.length; i++) - ui8[i] = binStr.charCodeAt(i); - var src; - try { - let img = bipf_decode(buf, 0); - // console.log('got svg', JSON.stringify(img)); - // img[0] -- version of this svg encoding, currently 1, ignored - if (Number.isInteger(img[0])) - img = img.slice(1) - var svg = ` { - if (e[0] == 'c') { - colNdx = e[1]; - } else if (e[0] == 'w') { - widNdx = e[1]; - } else if (e[0] == 'p') { - svg += `` - } - }) - svg += ''; - // console.log('svg:', svg) - src = `data:image/svg+xml;base64,${btoa(svg)}`; - } catch (error) { - console.error(error); - src = "data:null"; - } - box += `  "); - // txt = txt + "    (!)"; - // console.log(txt); - } - if (p.voice != null) - box += "🔊  " - box += txt - var d = new Date(p["when"]); - d = d.toDateString() + ' ' + d.toTimeString().substring(0, 5); - box += "
"; - box += d + "
"; - var row; - if (is_other) { - var c = tremola.contacts[p.from] - row = "" - // row = ">" - row += "" + box + ""; - } else { - row = "" + box; - row += "<" - } - pl.insertRow(pl.rows.length).innerHTML = row; -} - -function load_chat(nm) { - var ch, pl, e; - ch = tremola.chats[nm] - if (ch.timeline == null) - ch["timeline"] = new Timeline(); - pl = document.getElementById("lst:posts"); - while (pl.rows.length) { - pl.deleteRow(0); - } - pl.insertRow(0).innerHTML = "     "; - curr_chat = nm; - for (var n of ch.timeline.linear) { - load_post_item(ch.posts[n.name]) - } - /* - var lop = []; // list of posts - for (var p in ch.posts) lop.push(p) - lop.sort(function (a, b) { - return ch.posts[a].when - ch.posts[b].when - }) - lop.forEach(function (p) { - load_post_item(ch.posts[p]) - }) - */ - load_chat_title(ch); - setScenario("posts"); - document.getElementById("tremolaTitle").style.display = 'none'; - // update unread badge: - ch["lastRead"] = Date.now(); - persist(); - document.getElementById(nm + '-badge').style.display = 'none' // is this necessary? - /* - // scroll to bottom: - var c = document.getElementById('core'); - c.scrollTop = c.scrollHeight; - document.getElementById('lst:posts').scrollIntoView(false) - // console.log("did scroll down, but did it do it?") - */ -} - -function load_chat_title(ch) { - var c = document.getElementById("conversationTitle"), bg, box; - c.style.display = null; - c.setAttribute('classList', ch.forgotten ? 'gray' : '') // old JS (SDK 23) - box = "
" + escapeHTML(ch.alias) + "
"; - var mem = "[ALL]" - if (ch.members.length > 1 || ch.members[0] != "ALL") - mem = "🔒 " + recps2display(ch.members) - box += "
" + escapeHTML(mem) + "
"; - c.innerHTML = box; -} - -function load_chat_list() { - document.getElementById('lst:chats').innerHTML = ''; - load_chat_item("ALL") - var meOnly = recps2nm([myId]) - if (!(meOnly in tremola.chats)) { - tremola.chats[meOnly] = { - "alias": "--- local notes (for my eyes only)", "posts": {}, "forgotten": false, - "members": [myId], "touched": Date.now(), "lastRead": 0, - "timeline": new Timeline() - }; - } - load_chat_item(meOnly) - var lop = []; - for (var p in tremola.chats) { - if (p != "ALL" && p != meOnly && !tremola.chats[p]['forgotten']) - lop.push(p) - } - lop.sort(function (a, b) { - return tremola.chats[b]["touched"] - tremola.chats[a]["touched"] - }) - lop.forEach(function (p) { - load_chat_item(p) - }) - // forgotten chats: unsorted - if (!tremola.settings.hide_forgotten_conv) - for (var p in tremola.chats) - if (p != meOnly && tremola.chats[p]['forgotten']) - load_chat_item(p) -} - -function load_chat_item(nm) { // appends a button for conversation with name nm to the conv list - var cl, mem, item, bg, row, badge, badgeId, cnt; - cl = document.getElementById('lst:chats'); - // console.log(nm) - if (nm == "ALL") - mem = "ALL"; - else - mem = "🔒 " + recps2display(tremola.chats[nm].members); - item = document.createElement('div'); - // item.style = "padding: 0px 5px 10px 5px; margin: 3px 3px 6px 3px;"; - item.setAttribute('class', 'chat_item_div'); // old JS (SDK 23) - if (tremola.chats[nm].forgotten) bg = ' gray'; else bg = ' light'; - row = ""; - row += "" - item.innerHTML = row; - cl.appendChild(item); - set_chats_badge(nm) -} - -function load_contact_list() { - document.getElementById("lst:contacts").innerHTML = ''; - for (var id in tremola.contacts) - if (!tremola.contacts[id].forgotten) - load_contact_item([id, tremola.contacts[id]]); - if (!tremola.settings.hide_forgotten_contacts) - for (var id in tremola.contacts) { - var c = tremola.contacts[id] - if (c.forgotten) - load_contact_item([id, c]); - } -} - -function load_contact_item(c) { // [ id, { "alias": "thealias", "initial": "T", "color": "#123456" } ] } - var row, item = document.createElement('div'), bg; - item.setAttribute('style', 'padding: 0px 5px 10px 5px;'); // old JS (SDK 23) - if (!("initial" in c[1])) { - c[1]["initial"] = c[1].alias.substring(0, 1).toUpperCase(); - persist(); - } - if (!("color" in c[1])) { - c[1]["color"] = colors[Math.floor(colors.length * Math.random())]; - persist(); - } - // console.log("load_c_i", JSON.stringify(c[1])) - bg = c[1].forgotten ? ' gray' : ' light'; - row = ""; - row += ""; - // var row = ""; - // console.log(row); - item.innerHTML = row; - document.getElementById('lst:contacts').appendChild(item); -} - function fill_members() { var choices = ''; for (var m in tremola.contacts) { @@ -636,179 +229,6 @@ function fill_members() { document.getElementById(myId).disabled = true; } -function show_contact_details(id) { - if (id == myId) { - document.getElementById('old_contact_alias_hdr').innerHTML = "Alias: (own name, visible to others)" - } else { - document.getElementById('old_contact_alias_hdr').innerHTML = "Alias: (only you can see this alias)" - } - var c = tremola.contacts[id]; - new_contact_id = id; - document.getElementById('old_contact_alias').value = c.alias ? c['alias'] : ""; - var details = ''; - details += '
IAM-Alias:  ' + (c.iam != "" ? c.iam : "—") + '
\n'; - details += '
Shortname:  ' + id2b32(id) + '
\n'; - details += '
SSB identity:  ' + id + '
\n'; - details += '
Forget this contact
' - document.getElementById('old_contact_details').innerHTML = details; - document.getElementById('old_contact-overlay').style.display = 'initial'; - document.getElementById('overlay-bg').style.display = 'initial'; - document.getElementById('hide_contact').checked = c.forgotten; - - document.getElementById('old_contact_alias').focus(); - overlayIsActive = true; -} - -function toggle_forget_contact(e) { - var c = tremola.contacts[new_contact_id]; - c.forgotten = !c.forgotten; - persist(); - closeOverlay(); - load_contact_list(); -} - -function save_content_alias() { - var c = tremola.contacts[new_contact_id]; - var val = document.getElementById('old_contact_alias').value; - var deleteAlias = false - - val.trim() - - if (val == '') { - deleteAlias = true - if (c.iam != "" && new_contact_id != myId) { - val = c.iam - } else { - val = id2b32(new_contact_id); - } - } - var old_alias = c.alias - c.alias = val; - c.initial = val.substring(0, 1).toUpperCase(); - c.color = colors[Math.floor(colors.length * Math.random())]; - - // update names in connected devices menu - for (var l in localPeers) { - if (localPeers[l].alias == old_alias) { - localPeers[l].alias = val - refresh_connection_entry(l) - } - } - - // share new alias with others via IAM message - if(new_contact_id == myId) { - if(deleteAlias) { - backend("iam " + btoa("")) - c.iam = "" - } else { - backend("iam " + btoa(val)) - c.iam = val - } - } - - persist(); - menu_redraw(); - closeOverlay(); -} - -// --- productivity - -function load_prod_list() { - document.getElementById("lst:prod").innerHTML = ''; - load_prod_item('Kanban', 'img/kanban.svg', 'setScenario("kanban")', - 'text text text h aghjwd gldfhjs hlgsf hgljksf hgls fdhglf sdhgl hfgskj hls dfhgjl shgjkls hgl sfdhgjk sdfjklg hljs hfgl dfjlsfs'); - load_prod_item('Scheduler', 'img/schedule.svg', 'xyz', - 'text text text h aghjwd gldfhjs hlgsf hgljksf hgls fdhglf sdhgl hfgskj hls dfhgjl shgjkls hgl sfdhgjk sdfjklg hljs hfgl dfjlsfs'); - load_prod_item('Kahoot Quiz', 'img/quiz.svg', 'xyz', - 'text text text h aghjwd gldfhjs hlgsf hgljksf hgls fdhglf sdhgl hfgskj hls dfhgjl shgjkls hgl sfdhgjk sdfjklg hljs hfgl dfjlsfs'); - load_prod_item('Lokens (coming soon)', 'img/hand_and_coins.svg', 'xyz', - 'crypto tokens based on CRDTs, no mining needed. Ideal for fidelity cards, bartering, recognition tokens in open SW communities, and more.'); - load_prod_item('(dummy entry)', 'img/contacts.svg', 'xyz', - 'dah dah dah'); -} - -function load_prod_item(title, imageName, cb, descr) { - var row, item = document.createElement('div'), bg; - item.setAttribute('style', 'padding: 0px 5px 10px 5px;'); // old JS (SDK 23) - bg = ' light'; // c[1].forgotten ? ' gray' : ' light'; - row = `"; - item.innerHTML = row; - document.getElementById('lst:prod').appendChild(item); -} - -// --- games - -function load_game_list() { - document.getElementById("lst:games").innerHTML = ''; - load_game_item('Battelship (dpi24.06)', 'games/dpi24-06-battelship/battleship.svg', - 'text text text h aghjwd gldfhjs hlgsf hgljksf hgls fdhglf sdhgl hfgskj hls dfhgjl shgjkls hgl sfdhgjk sdfjklg hljs hfgl dfjlsfs'); - load_game_item('Snake (dpi24.07)', 'games/dpi24-07-snake/snake.svg', - 'text text text h aghjwd gldfhjs hlgsf hgljksf hgls fdhglf sdhgl hfgskj hls dfhgjl shgjkls hgl sfdhgjk sdfjklg hljs hfgl dfjlsfs'); - load_game_item('Checkers (dpi24.08)', 'games/dpi24-08-checkers/checkers.svg', - 'text text text h aghjwd gldfhjs hlgsf hgljksf hgls fdhglf sdhgl hfgskj hls dfhgjl shgjkls hgl sfdhgjk sdfjklg hljs hfgl dfjlsfs'); - load_game_item('Connect4 (dpi24.09)', 'games/dpi24-09-connect4/connect4.png', - 'text text text h aghjwd gldfhjs hlgsf hgljksf hgls fdhglf sdhgl hfgskj hls dfhgjl shgjkls hgl sfdhgjk sdfjklg hljs hfgl dfjlsfs'); - load_game_item('Blackjack (dpi24.10)', 'games/dpi24-10-blackjack/coins.svg', - 'crypto tokens based on CRDTs, no mining needed. Ideal for fidelity cards, bartering, recognition tokens in open SW communities, and more.'); - load_game_item('Hangman (dpi24.11)', 'games/dpi24-11-hangman/hangman.svg', - 'dah dah dah'); -} - -function load_game_item(title, imageName, descr) { - var row, item = document.createElement('div'), bg; - item.setAttribute('style', 'padding: 0px 5px 10px 5px;'); // old JS (SDK 23) - bg = ' light'; // c[1].forgotten ? ' gray' : ' light'; - row = `"; - item.innerHTML = row; - document.getElementById('lst:games').appendChild(item); -} - -// --- chats - -function new_conversation() { - // { "alias":"local notes (for my eyes only)", "posts":{}, "members":[myId], "touched": millis } - var recps = [] - for (var m in tremola.contacts) { - if (document.getElementById(m).checked) - recps.push(m); - } - if (recps.indexOf(myId) < 0) - recps.push(myId); - if (recps.length > 7) { - launch_snackbar("Too many recipients"); - return; - } - var cid = recps2nm(recps) - if (cid in tremola.chats) { - if (tremola.chats[cid].forgotten) { - tremola.chats[cid].forgotten = false; - load_chat_list(); // refresh - } else - launch_snackbar("Conversation already exists"); - return; - } - var nm = recps2nm(recps); - if (!(nm in tremola.chats)) { - tremola.chats[nm] = { - "alias": "Private", "posts": {}, - "members": recps, "touched": Date.now(), - "timeline": new Timeline() - }; - persist(); - } else - tremola.chats[nm]["touched"] = Date.now() - load_chat_list(); - setScenario("chats") - curr_chat = nm - menu_edit_convname() -} - function load_peer_list() { var i, lst = '', row; for (i in localPeers) { @@ -837,27 +257,6 @@ function show_peer_details(id) { menu_edit("trust_wifi_peer", "Trust and Autoconnect
 
" + new_contact_id + "
 
Should this WiFi peer be trusted (and autoconnected to)? Also enter an alias for the peer - only you will see this alias", "?") } -function getUnreadCnt(nm) { - var c = tremola.chats[nm], cnt = 0; - for (var p in c.posts) { - if (c.posts[p].when > c.lastRead) - cnt++; - } - return cnt; -} - -function set_chats_badge(nm) { - var e = document.getElementById(nm + '-badge'), cnt; - cnt = getUnreadCnt(nm) - if (cnt == 0) { - e.style.display = 'none'; - return - } - e.style.display = null; - if (cnt > 9) cnt = ">9"; else cnt = "" + cnt; - e.innerHTML = cnt -} - // --- util function unicodeStringToTypedArray(s) {