fix profile page
This commit is contained in:
@@ -9,7 +9,36 @@
|
||||
font-size: 5rem;
|
||||
}
|
||||
|
||||
#avatar header.avatar_only {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 2rem 1rem 1rem;
|
||||
}
|
||||
|
||||
#avatar header.avatar_only span.primary {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
form[name=bannerform] img.avatar {
|
||||
max-width: calc(100% - 2rem);
|
||||
margin: 1rem;
|
||||
}
|
||||
#avatar header.avatar_only {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 2rem 1rem 1rem;
|
||||
}
|
||||
|
||||
#avatar header.avatar_only span.primary {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
+2
-7
@@ -1,6 +1,6 @@
|
||||
//! Shared application state and the per-call context given to the widgets.
|
||||
|
||||
use minijinja::{context, Environment, Value as JValue};
|
||||
use minijinja::Environment;
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
@@ -193,12 +193,7 @@ impl Ctx {
|
||||
return String::new();
|
||||
}
|
||||
};
|
||||
let ctx = context! {
|
||||
lang => self.lang(),
|
||||
base_uri => self.base_uri(),
|
||||
me => self.jid(),
|
||||
..JValue::from_serialize(&data)
|
||||
};
|
||||
let ctx = crate::tpl::render_context(serde_json::to_value(&data).unwrap_or_default(), &self.lang(), &self.base_uri(), &self.jid());
|
||||
match tpl.render(ctx) {
|
||||
Ok(s) => s.trim().to_string(),
|
||||
Err(e) => {
|
||||
|
||||
+24
@@ -91,6 +91,16 @@ fn add_emojis(state: &State, s: String, no_title: Option<bool>) -> Value {
|
||||
Value::from_safe_string(utils::replace_emojis(&escaped, &base, no_title.unwrap_or(false)).html)
|
||||
}
|
||||
|
||||
/// The widget data wins over the defaults (the Avatar templates pass their own `me`)
|
||||
pub fn render_context(mut data: serde_json::Value, lang: &str, base_uri: &str, me: &str) -> Value {
|
||||
if let Some(map) = data.as_object_mut() {
|
||||
for (k, v) in [("lang", lang), ("base_uri", base_uri), ("me", me)] {
|
||||
map.entry(k).or_insert_with(|| v.into());
|
||||
}
|
||||
}
|
||||
Value::from_serialize(&data)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -116,4 +126,18 @@ mod tests {
|
||||
let html = tpl.render(data("vi")).unwrap();
|
||||
assert!(html.contains("Ghi âm") && html.contains("Tệp đã chia sẻ"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_picture_header() {
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
i18n::init(&root.join("frontend"));
|
||||
let env = environment(&root.join("templates"));
|
||||
let tpl = env.get_template("Avatar/_avatar.html").unwrap();
|
||||
let data = json!({ "me": { "picture": "/avatar/abc", "color": "yellow" } });
|
||||
let html = tpl.render(render_context(data, "en", "/", "alice@example.org")).unwrap();
|
||||
assert!(html.contains("url(/avatar/abc)") && html.contains("color yellow"), "{html}");
|
||||
// The defaults are still there
|
||||
let html = env.render_str("{{ me }} {{ lang }}", render_context(json!({}), "vi", "/", "alice@example.org")).unwrap();
|
||||
assert_eq!(html, "alice@example.org vi");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
<header class="big top color {{ me.color }}" style="background-image: url({{ me.picture }})">
|
||||
<ul class="list thick">
|
||||
<li class="block">
|
||||
<span class="primary icon bubble color {{ me.color }}" style="background-image: url({{ me.picture }})">
|
||||
</span>
|
||||
<div>
|
||||
<a class="button color transparent active" onclick="Avatar_ajaxGetForm()" title="{{ __('avatar.change') }}">
|
||||
<i class="material-symbols">person_edit</i>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<header class="avatar_only">
|
||||
<span class="primary icon bubble color {{ me.color }}" style="background-image: url({{ me.picture }})"
|
||||
onclick="Avatar_ajaxGetForm()" title="{{ __('avatar.change') }}">
|
||||
</span>
|
||||
<button class="button flat" onclick="Avatar_ajaxGetForm()">
|
||||
<i class="material-symbols">person_edit</i>
|
||||
{{ __('avatar.change') }}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Set a profile picture and check it on the profile (configuration) page."""
|
||||
import asyncio, os
|
||||
from playwright.async_api import async_playwright
|
||||
from browser import login, BASE, OUT
|
||||
|
||||
S = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
async def header(page):
|
||||
return await page.evaluate("""() => {
|
||||
const h = document.querySelector('#avatar header');
|
||||
const s = document.querySelector('#avatar header span.primary');
|
||||
return h ? [getComputedStyle(h).backgroundImage, s && getComputedStyle(s).backgroundImage,
|
||||
h.getBoundingClientRect().height] : null;
|
||||
}""")
|
||||
|
||||
|
||||
async def main():
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch()
|
||||
ctx, page, errors = await login(browser, "alice@localhost", "alicepass", "avatar")
|
||||
bad = []
|
||||
page.on("response", lambda r: r.status >= 400 and bad.append(f"{r.status} {r.url}"))
|
||||
await page.goto(BASE + "/configuration")
|
||||
await page.wait_for_timeout(4000)
|
||||
print("before:", await header(page))
|
||||
await page.screenshot(path=f"{OUT}/avatar_1_before.png")
|
||||
|
||||
await page.evaluate("Avatar_ajaxGetForm()")
|
||||
await page.wait_for_selector("#dialog input[type=file]", state="attached", timeout=10000)
|
||||
await page.set_input_files("#dialog input[type=file]", f"{S}/test.png")
|
||||
await page.wait_for_timeout(2000)
|
||||
print("photobin length:", await page.evaluate("document.querySelector('[name=photobin]').value.length"))
|
||||
await page.screenshot(path=f"{OUT}/avatar_2_form.png")
|
||||
await page.click("#dialog footer button:last-child")
|
||||
await page.wait_for_timeout(4000)
|
||||
print("after submit:", await header(page))
|
||||
await page.screenshot(path=f"{OUT}/avatar_3_after.png")
|
||||
|
||||
await page.goto(BASE + "/configuration")
|
||||
await page.wait_for_timeout(4000)
|
||||
print("after reload:", await header(page))
|
||||
print("nav avatar:", await page.evaluate("[...document.querySelectorAll('nav img, nav span.primary')].map(e => e.src || getComputedStyle(e).backgroundImage).slice(0, 3)"))
|
||||
await page.screenshot(path=f"{OUT}/avatar_4_reload.png")
|
||||
for e in errors + bad:
|
||||
print(e)
|
||||
await browser.close()
|
||||
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user