hyaenidae/server/src/profiles/state.rs
2021-01-24 15:30:02 -06:00

425 lines
13 KiB
Rust

use crate::{
error::{Error, OptionExt},
extensions::{ProfileExt, SubmissionExt},
images::{BannerImage, IconImage},
pagination::{
submission::{draft_page, main_page, Cache},
PageSource,
},
views::ProfileView,
State,
};
use hyaenidae_profiles::store::{File, Profile};
use hyaenidae_toolkit::{Button, FileInput, IndicatorColor, TextInput, Tile};
use uuid::Uuid;
const ACCEPT_TYPES: &str = "image/png,image/webp,image/jpeg,image/gif,.png,.webp,.jpg,.jpeg,.gif";
pub struct ViewProfileState {
cache: Cache,
profile_id: Uuid,
submissions: Vec<Uuid>,
previous_id: Option<Uuid>,
next_id: Option<Uuid>,
reset: bool,
path: String,
drafts: bool,
is_self: bool,
is_follow_requested: bool,
is_followed: bool,
is_blocked: bool,
viewer_exists: bool,
}
pub struct EditProfileState {
pub(crate) profile: Profile,
icon: Option<File>,
banner: Option<File>,
display_name_value: Option<String>,
display_name_error: Option<String>,
description_value: Option<String>,
description_error: Option<String>,
pub(crate) icon_error: Option<String>,
pub(crate) banner_error: Option<String>,
login_required_value: Option<bool>,
pub(crate) login_required_error: Option<String>,
}
impl ViewProfileState {
fn unwrap_profile(&self) -> &Profile {
self.cache.profile_map.get(&self.profile_id).unwrap()
}
pub(crate) fn profile<'a>(&'a self) -> ProfileView<'a> {
let profile = self.cache.profile_map.get(&self.profile_id).unwrap();
let icon = if let Some(icon) = profile.icon() {
self.cache.file_map.get(&icon)
} else {
None
};
let banner = if let Some(banner) = profile.banner() {
self.cache.file_map.get(&banner)
} else {
None
};
ProfileView {
profile,
icon,
banner,
}
}
pub(crate) fn submissions(&self) -> Vec<Tile> {
self.submissions
.iter()
.filter_map(move |submission_id| {
let submission = self.cache.submission_map.get(&submission_id)?;
let author = self.cache.profile_map.get(&submission.profile_id())?;
let file_id = submission.files().get(0)?;
let file = self.cache.file_map.get(&file_id)?;
let key = file.pictrs_key()?;
let tile = Tile::new(IconImage::new(key, submission.title()))
.title(&submission.title_text())
.description(&author.name())
.link(&submission.view_path());
if submission.files().len() > 1 {
Some(tile.indicator(
&format!("+{}", submission.files().len() - 1),
IndicatorColor::White,
))
} else {
Some(tile)
}
})
.collect()
}
pub(crate) fn buttons(&self) -> Vec<Button> {
let mut btns = vec![];
if self.is_self && self.drafts {
btns.push(Button::secondary("View Profile").href(&self.unwrap_profile().view_path()));
btns.push(Button::secondary("Edit Profile").href("/profiles/current"));
btns.push(Button::secondary("Switch Profile").href("/profiles/change"));
} else if self.is_self {
btns.push(Button::secondary("View Drafts").href("/profiles/drafts"));
btns.push(Button::secondary("Edit Profile").href("/profiles/current"));
btns.push(Button::secondary("Switch Profile").href("/profiles/change"));
} else if self.is_follow_requested {
btns.push(
Button::secondary("Remove Request").form(&self.unwrap_profile().unfollow_path()),
);
btns.push(Button::secondary("Block").form(&self.unwrap_profile().block_path()));
btns.push(Button::secondary("Report").href(&self.unwrap_profile().report_path()));
} else if self.is_followed {
btns.push(Button::secondary("Unfollow").form(&self.unwrap_profile().unfollow_path()));
btns.push(Button::secondary("Block").form(&self.unwrap_profile().block_path()));
btns.push(Button::secondary("Report").href(&self.unwrap_profile().report_path()));
} else if self.is_blocked {
btns.push(Button::secondary("Unblock").form(&self.unwrap_profile().unblock_path()));
btns.push(Button::secondary("Report").href(&self.unwrap_profile().report_path()));
} else if self.viewer_exists {
btns.push(Button::secondary("Follow").form(&self.unwrap_profile().follow_path()));
btns.push(Button::secondary("Block").form(&self.unwrap_profile().block_path()));
btns.push(Button::secondary("Report").href(&self.unwrap_profile().report_path()));
}
return btns;
}
pub(crate) fn nav(&self) -> Vec<Button> {
let mut nav = vec![];
if let Some(prev) = self.previous_id {
nav.push(Button::secondary("Previous").href(&format!("{}?min={}", self.path, prev)));
}
if let Some(next) = self.next_id {
nav.push(Button::secondary("Next").href(&format!("{}?max={}", self.path, next)));
}
if self.reset {
nav.push(Button::secondary("Reset").href(&self.path));
}
nav
}
pub(super) async fn for_profile(
path: String,
profile: Profile,
viewer: Option<Uuid>,
drafts: bool,
source: Option<PageSource>,
state: &State,
) -> Result<Self, Error> {
let store = state.profiles.clone();
let state = actix_web::web::block(move || {
let mut cache = Cache::new();
if let Some(icon) = profile.icon() {
if !cache.file_map.contains_key(&icon) {
let file = store.store.files.by_id(icon)?.req()?;
cache.file_map.insert(file.id(), file);
}
}
if let Some(banner) = profile.banner() {
if !cache.file_map.contains_key(&banner) {
let file = store.store.files.by_id(banner)?.req()?;
cache.file_map.insert(file.id(), file);
}
}
let profile_id = profile.id();
cache.profile_map.insert(profile.id(), profile);
let page = if drafts {
draft_page(viewer, profile_id, &store, &mut cache, source)
} else {
main_page(viewer, profile_id, &store, &mut cache, source)
};
let is_self = viewer.map(|id| id == profile_id).unwrap_or(false);
let is_follow_requested = if let Some(viewer_id) = viewer {
store
.store
.view
.follow_requests
.by_forward(profile_id, viewer_id)?
.is_some()
} else {
false
};
let is_followed = if let Some(viewer_id) = viewer {
store
.store
.view
.follows
.by_forward(profile_id, viewer_id)?
.is_some()
} else {
false
};
let is_blocked = if let Some(viewer_id) = viewer {
store
.store
.view
.blocks
.by_forward(profile_id, viewer_id)?
.is_some()
} else {
false
};
Ok(ViewProfileState {
cache,
profile_id,
submissions: page.items,
next_id: page.next,
previous_id: page.prev,
reset: page.reset,
path,
drafts,
is_self,
is_follow_requested,
is_followed,
is_blocked,
viewer_exists: viewer.is_some(),
}) as Result<ViewProfileState, Error>
})
.await?;
Ok(state)
}
}
impl EditProfileState {
pub(super) fn new(profile: Profile, icon: Option<File>, banner: Option<File>) -> Self {
EditProfileState {
profile,
icon,
banner,
display_name_value: None,
display_name_error: None,
description_value: None,
description_error: None,
icon_error: None,
banner_error: None,
login_required_value: None,
login_required_error: None,
}
}
pub(super) async fn for_profile(profile: Profile, state: &State) -> Result<Self, Error> {
let store = state.profiles.clone();
let icon_id = profile.icon();
let banner_id = profile.banner();
let (icon, banner) = actix_web::web::block(move || {
let icon = if let Some(id) = icon_id {
store.store.files.by_id(id)?
} else {
None
};
let banner = if let Some(id) = banner_id {
store.store.files.by_id(id)?
} else {
None
};
Ok((icon, banner)) as Result<_, Error>
})
.await?;
Ok(Self::new(profile, icon, banner))
}
pub(crate) fn profile<'a>(&'a self) -> ProfileView<'a> {
ProfileView {
profile: &self.profile,
icon: self.icon.as_ref(),
banner: self.banner.as_ref(),
}
}
pub(crate) fn display_name(&self, dark: bool) -> TextInput {
let input = TextInput::new("display_name")
.title("Display Name")
.placeholder("Display Name")
.dark(dark);
let input = if let Some(text) = &self.profile.display_name() {
input.value(text)
} else {
input
};
let input = if let Some(text) = &self.display_name_value {
input.value(text)
} else {
input
};
if let Some(text) = &self.display_name_error {
input.error_opt(Some(text.to_owned()))
} else {
input
}
}
pub(crate) fn description(&self, dark: bool) -> TextInput {
let input = TextInput::new("description")
.title("Description")
.placeholder("Description")
.textarea()
.dark(dark);
let input = if let Some(text) = &self.profile.description_text() {
input.value(text)
} else {
input
};
let input = if let Some(text) = &self.description_value {
input.value(text)
} else {
input
};
if let Some(text) = &self.description_error {
input.error_opt(Some(text.to_owned()))
} else {
input
}
}
pub(crate) fn icon_image(&self) -> Option<IconImage> {
let icon = self.icon.as_ref()?;
let key = icon.pictrs_key()?;
Some(IconImage::new(
key,
&format!("{}'s icon", self.profile.name()),
))
}
pub(crate) fn banner_image(&self) -> Option<BannerImage> {
let banner = self.banner.as_ref()?;
let key = banner.pictrs_key()?;
Some(BannerImage::new(
key,
&format!("{}'s icon", self.profile.name()),
))
}
pub(crate) fn icon(&self, dark: bool) -> FileInput {
FileInput::secondary("images[]", "Select Icon")
.accept(ACCEPT_TYPES)
.no_group()
.dark(dark)
}
pub(crate) fn banner(&self, dark: bool) -> FileInput {
FileInput::secondary("images[]", "Select Banner")
.accept(ACCEPT_TYPES)
.no_group()
.dark(dark)
}
pub(crate) fn login_required(&self) -> bool {
self.login_required_value
.unwrap_or(self.profile.login_required())
}
pub(super) fn display_name_value(&mut self, text: String) -> &mut Self {
self.display_name_value = Some(text);
self
}
pub(super) fn display_name_error(&mut self, err: String) -> &mut Self {
self.display_name_error = Some(err);
self
}
pub(super) fn description_value(&mut self, text: String) -> &mut Self {
self.description_value = Some(text);
self
}
pub(super) fn description_error(&mut self, err: String) -> &mut Self {
self.description_error = Some(err);
self
}
pub(super) fn icon_error(&mut self, err: &str) -> &mut Self {
self.icon_error = Some(err.to_owned());
self
}
pub(super) fn banner_error(&mut self, err: &str) -> &mut Self {
self.banner_error = Some(err.to_owned());
self
}
pub(super) fn login_required_value(&mut self, login_required: bool) -> &mut Self {
self.login_required_value = Some(login_required);
self
}
pub(super) fn login_required_error(&mut self, err: &str) -> &mut Self {
self.login_required_error = Some(err.to_owned());
self
}
}