hyaenidae/src/profiles/state.rs
asonix 010dd2952f Server: Expose NSFW toggle, Dark Mode toggle
Ensure all submission view permission logic is the same
2021-02-03 21:09:25 -06:00

538 lines
17 KiB
Rust

use crate::{
error::{Error, OptionExt},
extensions::{ProfileExt, SubmissionExt},
images::{BannerImage, IconImage},
pagination::{
submission::{draft_page, main_page, Cache},
PageSource,
},
profiles::settings::Settings,
views::ProfileView,
ActixLoader, State,
};
use hyaenidae_profiles::store::{File, Profile};
use hyaenidae_toolkit::{Button, FileInput, IndicatorColor, TextInput, Tile};
use i18n_embed_fl::fl;
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>,
pub(crate) settings_error: Option<String>,
pub(crate) settings: Settings,
}
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());
let sensitive_color = if submission.is_sensitive() {
Some(IndicatorColor::Red)
} else {
None
};
if submission.files().len() > 1 {
Some(tile.indicator(
&format!("+{}", submission.files().len() - 1),
sensitive_color.unwrap_or(IndicatorColor::White),
))
} else {
if let Some(sensitive_color) = sensitive_color {
Some(tile.indicator("", sensitive_color))
} else {
Some(tile)
}
}
})
.collect()
}
fn view_profile_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "view-profile-button"))
.href(&self.unwrap_profile().view_path())
}
fn account_settings_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "account-settings-button")).href("/session/account")
}
fn edit_profile_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "edit-profile-button")).href("/profiles/current")
}
fn switch_profile_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "switch-profile-button")).href("/profiles/change")
}
fn view_drafts_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "drafts-button")).href("/profiles/drafts")
}
fn cancel_request_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "cancel-follow-button"))
.href(&self.unwrap_profile().unfollow_path())
}
fn block_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "block-button")).form(&self.unwrap_profile().block_path())
}
fn report_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "report-button")).href(&self.unwrap_profile().report_path())
}
fn unfollow_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "unfollow-button"))
.form(&self.unwrap_profile().unfollow_path())
}
fn unblock_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "unblock-button"))
.form(&self.unwrap_profile().unblock_path())
}
fn follow_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "follow-button")).form(&self.unwrap_profile().follow_path())
}
pub(crate) fn buttons(&self, loader: &ActixLoader) -> Vec<Button> {
let mut btns = vec![];
if self.is_self && self.drafts {
btns.push(self.view_profile_button(loader));
btns.push(self.edit_profile_button(loader));
btns.push(self.switch_profile_button(loader));
btns.push(self.account_settings_button(loader));
} else if self.is_self {
btns.push(self.view_drafts_button(loader));
btns.push(self.edit_profile_button(loader));
btns.push(self.switch_profile_button(loader));
btns.push(self.account_settings_button(loader));
} else if self.is_follow_requested {
btns.push(self.cancel_request_button(loader));
btns.push(self.block_button(loader));
btns.push(self.report_button(loader));
} else if self.is_followed {
btns.push(self.unfollow_button(loader));
btns.push(self.block_button(loader));
btns.push(self.report_button(loader));
} else if self.is_blocked {
btns.push(self.unblock_button(loader));
btns.push(self.report_button(loader));
} else if self.viewer_exists {
btns.push(self.follow_button(loader));
btns.push(self.block_button(loader));
btns.push(self.report_button(loader));
}
return btns;
}
pub(crate) fn nav(&self, loader: &ActixLoader) -> Vec<Button> {
let mut nav = vec![];
if let Some(prev) = self.previous_id {
nav.push(
Button::secondary(&fl!(loader, "previous-button"))
.href(&format!("{}?min={}", self.path, prev)),
);
}
if let Some(next) = self.next_id {
nav.push(
Button::secondary(&fl!(loader, "next-button"))
.href(&format!("{}?max={}", self.path, next)),
);
}
if self.reset {
nav.push(Button::secondary(&fl!(loader, "reset-button")).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 can_view_sensitive = if let Some(viewer) = viewer {
state.settings.for_profile(viewer).await?.sensitive
} else {
false
};
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,
can_view_sensitive,
profile_id,
&store,
&mut cache,
source,
)
} else {
main_page(
viewer,
can_view_sensitive,
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>,
settings: Settings,
) -> 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,
settings_error: None,
settings,
}
}
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 settings = state.settings.for_profile(profile.id()).await?;
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, settings))
}
pub(crate) fn profile<'a>(&'a self) -> ProfileView<'a> {
ProfileView {
profile: &self.profile,
icon: self.icon.as_ref(),
banner: self.banner.as_ref(),
}
}
fn view_profile_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "view-profile-button")).href(&self.profile.view_path())
}
fn view_drafts_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "drafts-button")).href("/profiles/drafts")
}
fn switch_profile_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "switch-profile-button")).href("/profiles/change")
}
fn account_settings_button(&self, loader: &ActixLoader) -> Button {
Button::secondary(&fl!(loader, "account-settings-button")).href("/session/account")
}
pub(crate) fn buttons(&self, loader: &ActixLoader) -> Vec<Button> {
vec![
self.view_profile_button(loader),
self.view_drafts_button(loader),
self.switch_profile_button(loader),
self.account_settings_button(loader),
]
}
pub(crate) fn display_name(&self, loader: &ActixLoader) -> TextInput {
let input = TextInput::new("display_name")
.title(&fl!(loader, "create-bio-display-name"))
.placeholder(&fl!(loader, "create-bio-display-name-placeholder"))
.error_opt(self.display_name_error.clone());
let input = if let Some(text) = &self.profile.display_name_source() {
input.value(text)
} else {
input
};
if let Some(text) = &self.display_name_value {
input.value(text)
} else {
input
}
}
pub(crate) fn description(&self, loader: &ActixLoader) -> TextInput {
let input = TextInput::new("description")
.title(&fl!(loader, "create-bio-description-input"))
.placeholder(&fl!(loader, "create-bio-description-placeholder"))
.textarea()
.error_opt(self.description_error.clone());
let input = if let Some(text) = &self.profile.description_source() {
input.value(text)
} else {
input
};
if let Some(text) = &self.description_value {
input.value(text)
} else {
input
}
}
pub(crate) fn icon_image(&self, loader: &ActixLoader) -> Option<IconImage> {
let icon = self.icon.as_ref()?;
let key = icon.pictrs_key()?;
Some(IconImage::new(
key,
&fl!(loader, "profile-icon", profileName = self.profile.name()),
))
}
pub(crate) fn banner_image(&self, loader: &ActixLoader) -> Option<BannerImage> {
let banner = self.banner.as_ref()?;
let key = banner.pictrs_key()?;
Some(BannerImage::new(
key,
&fl!(loader, "profile-banner", profileName = self.profile.name()),
))
}
pub(crate) fn icon(&self, loader: &ActixLoader) -> FileInput {
FileInput::secondary("images[]", &fl!(loader, "create-icon-button"))
.accept(ACCEPT_TYPES)
.no_group()
}
pub(crate) fn banner(&self, loader: &ActixLoader) -> FileInput {
FileInput::secondary("images[]", &fl!(loader, "create-banner-button"))
.accept(ACCEPT_TYPES)
.no_group()
}
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
}
pub(super) fn settings_error(&mut self, err: String) -> &mut Self {
self.settings_error = Some(err);
self
}
}