diff --git a/web/css/styles.css b/web/css/styles.css index e713fe8..7ff273f 100644 --- a/web/css/styles.css +++ b/web/css/styles.css @@ -137,7 +137,7 @@ button.nav > img.icon { position: sticky; top: 0; z-index: var(--zHeader); - background: var(--clrBg); + backdrop-filter: blur(20px); } #view header > label { padding: 15px; @@ -337,6 +337,7 @@ details.cw summary { background: rgba(0,0,0,0.4); opacity: 1; transition: opacity 0.2s linear; + backdrop-filter: blur(20px); } .modal.closed { opacity: 0; @@ -440,19 +441,11 @@ label[role="profile-nip5"] { /* Media Preview */ -.bg-blur { - backdrop-filter: blur(20px); - position: absolute; - width: 100%; - height: 100%; - z-index: var(--zModal); -} .modal .media-container { text-align: center; position: absolute; width: 100%; height: 100%; - z-index: calc(var(--zModal) + 1); } .modal .media-container > img { object-fit: scale-down; diff --git a/web/index.html b/web/index.html index 51dd49e..60ef930 100644 --- a/web/index.html +++ b/web/index.html @@ -40,7 +40,7 @@ - @@ -84,7 +84,7 @@
-
+
@@ -107,31 +107,7 @@
-
- -
-
- -
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
- -
-
+
@@ -151,14 +127,18 @@

-
+
+
+ +
+
+
` } -function render_loading_spinner() -{ +function render_loading_spinner() { return `
diff --git a/web/js/ui/state.js b/web/js/ui/state.js index 5a6006f..50fc75f 100644 --- a/web/js/ui/state.js +++ b/web/js/ui/state.js @@ -13,25 +13,7 @@ function get_thread_max_depth(damus, view, root_id) { return view.depths[root_id] } -function shouldnt_render_event(our_pk, view, ev, opts) { - return !opts.is_composing && - !view.expanded.has(ev.id) && - view.rendered.has(ev.id) -} - -function toggle_content_warning(el) { - const id = el.id.split("_")[1] - const ev = DAMUS.all_events[id] - - if (!ev) { - log_debug("could not find content-warning event", id) - return - } - - DAMUS.cw_open[id] = el.open -} - -function expand_thread(id, reply_id) { +/*function expand_thread(id, reply_id) { const view = get_current_view() const root_id = get_thread_root_id(DAMUS, id) if (!root_id) { @@ -41,7 +23,7 @@ function expand_thread(id, reply_id) { view.expanded.add(reply_id) view.depths[root_id] = get_thread_max_depth(DAMUS, view, root_id) + 1 redraw_events(DAMUS, view) -} +}*/ function get_thread_root_id(damus, id) { const ev = damus.all_events[id] @@ -52,71 +34,18 @@ function get_thread_root_id(damus, id) { return ev.refs && ev.refs.root } -function redraw_events(damus, view) { - //log_debug("redrawing events for", view) - view.rendered = new Set() - const events_el = damus.view_el.querySelector(`#${view.name}-view > .events`) - events_el.innerHTML = render_events(damus, view) +function switch_view(mode, opts) { + log_warn("switch_view deprecated, use view_timeline_apply_mode"); + view_timeline_apply_mode(DAMUS, mode, opts); } -function redraw_timeline_events(damus, name) { - const view = DAMUS.views[name] - const events_el = damus.view_el.querySelector(`#${name}-view > .events`) - if (view.events.length > 0) { - redraw_events(damus, view) - } else { - events_el.innerHTML = render_loading_spinner() - } +function view_get_timeline_el() { + return find_node("#timeline"); } -function switch_view(name, opts={}) -{ - if (name === DAMUS.current_view) { - log_debug("Not switching to '%s', we are already there", name) - return - } - - const last = get_current_view() - if (!last) { - // render initial - DAMUS.current_view = name - redraw_timeline_events(DAMUS, name) - return - } - - log_debug("switching to '%s' by hiding '%s'", name, DAMUS.current_view) - - DAMUS.current_view = name - const current = get_current_view() - const last_el = get_view_el(last.name) - const current_el = get_view_el(current.name) - - if (last_el) - last_el.classList.add("hide"); - - // TODO accomodate views that do not render events - // TODO find out if having multiple event divs is slow - //redraw_timeline_events(DAMUS, name) - - find_node("#nav > div[data-active]").dataset.active = name; - - if (current_el) - current_el.classList.remove("hide"); -} - -function get_current_view() -{ - // TODO resolve memory & html descriptencies - // Currently there is tracking of which divs are visible in HTML/CSS and - // which is active in memory, simply resolve this by finding the visible - // element instead of tracking it in memory (or remove dom elements). This - // would simplify state tracking IMO - Thomas - return DAMUS.views[DAMUS.current_view] -} - -function view_timeline_update_profiles(model, state, ev) { +function view_timeline_update_profiles(model, ev) { let xs, html; - const el = find_node("#view #home-view .events"); + const el = view_get_timeline_el(); const pk = ev.pubkey; const p = model.profiles[pk]; @@ -140,8 +69,98 @@ function view_timeline_update_profiles(model, state, ev) { } } -function view_timeline_update(model, state) { - const el = find_node("#view #home-view .events"); +function view_timeline_update_timestamps(model) { + const el = view_get_timeline_el(); + let xs = el.querySelectorAll(".timestamp"); + let now = new Date().getTime(); + for (const x of xs) { + let t = parseInt(x.dataset.timestamp) + x.innerText = fmt_since_str(now, t*1000); + } +} + +function view_timeline_update_reaction(model, ev) { + // TODO loop through elements with ev reactions to and update them +} + +const VM_FRIENDS = "friends"; +const VM_EXPLORE = "explore"; +const VM_NOTIFICATIONS = "notifications"; +const VM_THREAD = "thread"; +const VM_USER = "user"; +// friends: mine + only events that are from my contacts +// explore: all events +// notifications: reactions & replys +// thread: all events in response to target event +// user: all events by pubkey + +function view_mode_contains_event(model, ev, mode, opts={}) { + switch(mode) { + case VM_EXPLORE: + return ev.kind != KIND_REACTION; + case VM_USER: + return opts.pubkey && ev.pubkey == opts.pubkey; + case VM_FRIENDS: + return ev.pubkey == model.pubkey || contact_is_friend(model.contacts, ev.pubkey); + case VM_THREAD: + return ev.id == opts.thread_id || (ev.refs && ( + ev.refs.root == opts.thread_id || + ev.refs.reply == opts.thread_id)); + //event_refs_event(ev, opts.thread_id); + case VM_NOTIFICATIONS: + return event_refs_pubkey(ev, model.pubkey); + } + return false; +} + +function view_timeline_apply_mode(model, mode, opts={}) { + let xs; + const { pubkey, thread_id } = opts; + const el = view_get_timeline_el(); + + el.dataset.mode = mode; + switch(mode) { + case VM_THREAD: + el.dataset.threadId = thread_id; + case VM_USER: + el.dataset.pubkey = pubkey; + break; + default: + delete el.dataset.threadId; + delete el.dataset.pubkey; + break; + } + + const names = {}; + names[VM_FRIENDS] = "Home"; + names[VM_EXPLORE] = "Explore"; + names[VM_NOTIFICATIONS] = "Notifications"; + names[VM_USER] = "Profile"; + names[VM_THREAD] = "Thread"; + find_node("#view header > label").innerText = mode == VM_USER ? render_name_plain(DAMUS.profiles[opts.pubkey]) : names[mode]; + find_node("#nav > div[data-active]").dataset.active = names[mode].toLowerCase(); + find_node("#view [role='profile-info']").classList.toggle("hide", mode != VM_USER); + find_node("#newpost").classList.toggle("hide", mode != VM_FRIENDS); + + xs = el.querySelectorAll(".event"); + for (const x of xs) { + let evid = x.id.substr(2); + let ev = model.all_events[evid]; + x.classList.toggle("hide", + !view_mode_contains_event(model, ev, mode, opts)); + } +} + +/* view_timeline_update iterates through invalidated event ids and either adds + * or removes them from the timeline. + */ +function view_timeline_update(model) { + const el = view_get_timeline_el(); + const mode = el.dataset.mode; + const opts = { + thread_id: el.dataset.threadId, + pubkey: el.dataset.pubkey, + }; // for each event not rendered, go through the list and render it marking // it as rendered and adding it to the appropriate fragment. fragments are @@ -151,27 +170,31 @@ function view_timeline_update(model, state) { // const cache = {}; // Dumb function to insert needed events + let visible_count = 0; const all = model_events_arr(model); - while (state.invalidated.length > 0) { - var evid = state.invalidated.pop(); + while (model.invalidated.length > 0) { + var evid = model.invalidated.pop(); var ev = model.all_events[evid]; - if (!event_is_renderable(ev)) { + if (!event_is_renderable(ev) || model_is_event_deleted(model, evid)) { // TODO check deleted let x = find_node("#ev"+evid, el); if (x) el.removeChild(x); continue; } - // TODO if event is not viewable for page, simply hide it - // if event is in el already, do nothing or update? let ev_el = find_node("#ev"+evid, el); if (ev_el) { continue; } else { let div = document.createElement("div"); - div.innerHTML = render_event2(model, ev, {}); + div.innerHTML = render_event(model, ev, {}); ev_el = div.firstChild; + if (!view_mode_contains_event(model, ev, mode, opts)) { + ev_el.classList.add("hide"); + } else { + visible_count++; + } } // find prior event element and insert it before that @@ -187,6 +210,9 @@ function view_timeline_update(model, state) { el.insertBefore(ev_el, prior_el); } } + + if (visible_count > 0) + find_node("#view .loading-events").classList.add("hide"); } function event_is_renderable(ev={}) { diff --git a/web/js/ui/util.js b/web/js/ui/util.js index 1502c15..bb13200 100644 --- a/web/js/ui/util.js +++ b/web/js/ui/util.js @@ -172,17 +172,16 @@ async function do_send_reply() { } function reply_to(evid) { + const ev = DAMUS.all_events[evid] const modal = document.querySelector("#reply-modal") const replybox = modal.querySelector("#reply-content") - modal.classList.remove("closed") const replying_to = modal.querySelector("#replying-to") - replying_to.dataset.evid = evid - - const ev = DAMUS.all_events[evid] - const view = get_current_view() - replying_to.innerHTML = render_event(DAMUS, view, ev, {is_composing: true, nobar: true, max_depth: 1}) - + replying_to.innerHTML = render_event_nointeract(DAMUS, ev, { + is_composing: true, + nobar: true + }); + modal.classList.remove("closed") replybox.focus() } @@ -190,7 +189,7 @@ function redraw_my_pfp(model, force = false) { const p = model.profiles[model.pubkey] if (!p) return; const html = render_pfp(model.pubkey, p); - const el = document.querySelector(".my-userpic") + const el = document.querySelector(".my-userpic"); if (!force && el.dataset.loaded) return; el.dataset.loaded = true; el.innerHTML = html; @@ -261,4 +260,35 @@ function get_privkey() { return privkey } +function open_thread(thread_id) { + view_timeline_apply_mode(DAMUS, VM_THREAD, { thread_id }); +} +function open_profile(pubkey) { + view_timeline_apply_mode(DAMUS, VM_USER, { pubkey }); + + const profile = DAMUS.profiles[pubkey]; + const el = find_node("[role='profile-info']"); + // TODO show loading indicator then render + + find_node("[role='profile-image']", el).src = get_picture(pubkey, profile); + find_nodes("[role='profile-name']", el).forEach(el => { + el.innerText = render_name_plain(profile); + }); + + const el_nip5 = find_node("[role='profile-nip5']", el) + el_nip5.innerText = profile.nip05; + el_nip5.classList.toggle("hide", !profile.nip05); + + const el_desc = find_node("[role='profile-desc']", el) + el_desc.innerHTML = newlines_to_br(profile.about); + el_desc.classList.toggle("hide", !profile.about); + + find_node("button[role='copy-pk']", el).dataset.pk = pubkey; + + const btn_follow = find_node("button[role='follow-user']", el) + btn_follow.dataset.pk = pubkey; + // TODO check follow status + btn_follow.innerText = contact_is_friend(DAMUS.contacts, pubkey) ? "Unfollow" : "Follow"; + btn_follow.classList.toggle("hide", pubkey == DAMUS.pubkey); +} diff --git a/web/js/util.js b/web/js/util.js index e382381..0cb6665 100644 --- a/web/js/util.js +++ b/web/js/util.js @@ -22,7 +22,7 @@ function safe_parse_json(data, message) { let value = undefined; try { value = JSON.parse(data); - } catch (e) { + } catch (err) { log_error(`${message} : unable to parse JSON`, err, data); } return value; @@ -151,6 +151,11 @@ function difficulty_to_prefix(d) { /* time_delta returns a string of the time of current since previous. */ function time_delta(current, previous) { + log_warn("time_delta deprecated, use fmt_since_str"); + fmt_since_str(current, previous); +} + +function fmt_since_str(current, previous) { var msPerMinute = 60 * 1000; var msPerHour = msPerMinute * 60; var msPerDay = msPerHour * 24;