add: 8 achievements add: larger text setting fix: small infinite

timeline bugs
This commit is contained in:
trisua 2025-06-27 13:10:04 -04:00
parent b860f74124
commit 5dd9fa01cb
19 changed files with 241 additions and 123 deletions

8
Cargo.lock generated
View file

@ -3231,7 +3231,7 @@ dependencies = [
[[package]] [[package]]
name = "tetratto" name = "tetratto"
version = "9.0.0" version = "10.0.0"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"async-stripe", "async-stripe",
@ -3262,7 +3262,7 @@ dependencies = [
[[package]] [[package]]
name = "tetratto-core" name = "tetratto-core"
version = "9.0.0" version = "10.0.0"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"base16ct", "base16ct",
@ -3284,7 +3284,7 @@ dependencies = [
[[package]] [[package]]
name = "tetratto-l10n" name = "tetratto-l10n"
version = "9.0.0" version = "10.0.0"
dependencies = [ dependencies = [
"pathbufd", "pathbufd",
"serde", "serde",
@ -3293,7 +3293,7 @@ dependencies = [
[[package]] [[package]]
name = "tetratto-shared" name = "tetratto-shared"
version = "9.0.0" version = "10.0.0"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"chrono", "chrono",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "tetratto" name = "tetratto"
version = "9.0.0" version = "10.0.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View file

@ -1,5 +1,17 @@
(div ("id" "toast_zone")) (div ("id" "toast_zone"))
; large text
(text "{% if user and user.settings.large_text -%}")
(style
(text "button, a, p, span, b, strone, em, i, pre, code {
font-size: 18px !important;
}
nav .icon {
font-size: 15px !important;
}"))
(text "{%- endif %}")
; templates ; templates
(template (template
("id" "loading_skeleton") ("id" "loading_skeleton")

View file

@ -49,6 +49,7 @@
(text "setTimeout(async () => { (text "setTimeout(async () => {
await trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?user_id={{ profile.id }}&tag={{ tag }}&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]); await trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?user_id={{ profile.id }}&tag={{ tag }}&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
(await ns(\"ui\")).IO_DATA_DISABLE_RELOAD = true; (await ns(\"ui\")).IO_DATA_DISABLE_RELOAD = true;
}, 500);")) console.log(\"created profile timeline\");
}, 1000);"))
(text "{% endblock %}") (text "{% endblock %}")

View file

@ -1407,6 +1407,22 @@
embed_html: embed_html:
'<span class=\"fade\">Muted phrases should all be on new lines.</span>', '<span class=\"fade\">Muted phrases should all be on new lines.</span>',
}], }],
[[], \"Accessibility\", \"title\"],
[
[\"large_text\", \"Increase UI text size\"],
\"{{ profile.settings.large_text }}\",
\"checkbox\",
],
[
[\"paged_timelines\", \"Make timelines paged instead of infinitely scrolled\"],
\"{{ profile.settings.paged_timelines }}\",
\"checkbox\",
],
[
[\"auto_clear_notifs\", \"Automatically clear all notifications when you open the notifications page\"],
\"{{ profile.settings.auto_clear_notifs }}\",
\"checkbox\",
],
], ],
settings, settings,
{ {
@ -1531,16 +1547,6 @@
\"Hides dislikes on all posts. Users will also no longer be able to dislike your posts.\", \"Hides dislikes on all posts. Users will also no longer be able to dislike your posts.\",
\"text\", \"text\",
], ],
[
[\"paged_timelines\", \"Make timelines paged instead of infinitely scrolled\"],
\"{{ profile.settings.paged_timelines }}\",
\"checkbox\",
],
[
[\"auto_clear_notifs\", \"Automatically clear all notifications when you open the notifications page\"],
\"{{ profile.settings.auto_clear_notifs }}\",
\"checkbox\",
],
[[], \"Fun\", \"title\"], [[], \"Fun\", \"title\"],
[ [
[\"disable_gpa_fun\", \"Disable GPA\"], [\"disable_gpa_fun\", \"Disable GPA\"],

View file

@ -1151,82 +1151,90 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
}); });
// intersection observer infinite scrolling // intersection observer infinite scrolling
const obs = (await ns("ui")).IO_DATA_OBSERVER; self.IO_DATA_OBSERVER = null;
if (obs) {
console.log("get lost old observer");
obs.disconnect();
}
self.IO_DATA_OBSERVER = new IntersectionObserver( self.define(
async (entries) => { "io_data_load",
for (const entry of entries) { async (_, tmpl, page, paginated_mode = false) => {
if (!entry.isIntersecting) { // remove old
continue; const obs = self.IO_DATA_OBSERVER;
} if (obs) {
console.log("get lost old observer");
await self.io_load_data(); obs.disconnect();
break; self.IO_DATA_OBSERVER = null;
} }
},
{
root: document.body,
rootMargin: "0px",
threshold: 1,
},
);
self.define("io_data_load", (_, tmpl, page, paginated_mode = false) => { self.IO_DATA_OBSERVER = new IntersectionObserver(
self.IO_DATA_MARKER = document.querySelector( async (entries) => {
"[ui_ident=io_data_marker]", for (const entry of entries) {
); if (!entry.isIntersecting) {
continue;
}
self.IO_DATA_ELEMENT = document.querySelector( await self.io_load_data();
"[ui_ident=io_data_load]", break;
); }
},
self.IO_HTML_TMPL = document.getElementById("loading_skeleton"); {
root: document.body,
if (!self.IO_DATA_ELEMENT || !self.IO_DATA_MARKER) { rootMargin: "0px",
console.warn( threshold: 1,
"ui::io_data_load called, but required elements don't exist", },
); );
return; // ...
} self.IO_DATA_MARKER = document.querySelector(
"[ui_ident=io_data_marker]",
);
self.IO_DATA_TMPL = tmpl; self.IO_DATA_ELEMENT = document.querySelector(
self.IO_DATA_PAGE = page; "[ui_ident=io_data_load]",
self.IO_DATA_SEEN_IDS = []; );
self.IO_DATA_WAITING = false;
self.IO_HAS_LOADED_AT_LEAST_ONCE = false;
self.IO_DATA_DISCONNECTED = false;
self.IO_DATA_DISABLE_RELOAD = false;
if (!paginated_mode) { self.IO_HTML_TMPL = document.getElementById("loading_skeleton");
self.IO_DATA_OBSERVER.observe(self.IO_DATA_MARKER);
} else { if (!self.IO_DATA_ELEMENT || !self.IO_DATA_MARKER) {
// immediately load first page console.warn(
self.IO_DATA_TMPL = self.IO_DATA_TMPL.replace("&page=", ""); "ui::io_data_load called, but required elements don't exist",
self.IO_DATA_TMPL += `&paginated=true&page=`; );
self.io_load_data();
}
setTimeout(() => {
if (self.IO_DATA_DISABLE_RELOAD) {
console.log("missing data reload disabled");
return; return;
} }
if (!self.IO_HAS_LOADED_AT_LEAST_ONCE) { self.IO_DATA_TMPL = tmpl;
// reload self.IO_DATA_PAGE = page;
self.IO_DATA_OBSERVER.disconnect(); self.IO_DATA_SEEN_IDS = [];
console.log("timeline load fail :("); self.IO_DATA_WAITING = false;
window.location.reload(); self.IO_HAS_LOADED_AT_LEAST_ONCE = false;
} self.IO_DATA_DISCONNECTED = false;
}, 1500); self.IO_DATA_DISABLE_RELOAD = false;
self.IO_PAGINATED = paginated_mode; if (!paginated_mode) {
}); self.IO_DATA_OBSERVER.observe(self.IO_DATA_MARKER);
} else {
// immediately load first page
self.IO_DATA_TMPL = self.IO_DATA_TMPL.replace("&page=", "");
self.IO_DATA_TMPL += `&paginated=true&page=`;
self.io_load_data();
}
setTimeout(() => {
if (self.IO_DATA_DISABLE_RELOAD) {
console.log("missing data reload disabled");
return;
}
if (!self.IO_HAS_LOADED_AT_LEAST_ONCE) {
// reload
self.IO_DATA_OBSERVER.disconnect();
console.log("timeline load fail :(");
window.location.reload();
}
}, 1500);
self.IO_PAGINATED = paginated_mode;
},
);
self.define("io_load_data", async () => { self.define("io_load_data", async () => {
if (self.IO_DATA_WAITING) { if (self.IO_DATA_WAITING) {

View file

@ -116,7 +116,7 @@ pub async fn update_user_settings_request(
Json(mut req): Json<UserSettings>, Json(mut req): Json<UserSettings>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let data = &(data.read().await).0; let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProfile) { let mut user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProfile) {
Some(ua) => ua, Some(ua) => ua,
None => return Json(Error::NotAllowed.into()), None => return Json(Error::NotAllowed.into()),
}; };
@ -153,7 +153,7 @@ pub async fn update_user_settings_request(
// award achievement // award achievement
if let Err(e) = data if let Err(e) = data
.add_achievement(&user, AchievementName::EditSettings.into()) .add_achievement(&mut user, AchievementName::EditSettings.into())
.await .await
{ {
return Json(e.into()); return Json(e.into());

View file

@ -22,7 +22,7 @@ pub async fn follow_request(
Extension(data): Extension<State>, Extension(data): Extension<State>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let data = &(data.read().await).0; let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageFollowing) { let mut user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageFollowing) {
Some(ua) => ua, Some(ua) => ua,
None => return Json(Error::NotAllowed.into()), None => return Json(Error::NotAllowed.into()),
}; };
@ -40,7 +40,7 @@ pub async fn follow_request(
} else { } else {
// create // create
match data match data
.create_userfollow(UserFollow::new(user.id, id), false) .create_userfollow(UserFollow::new(user.id, id), &user, false)
.await .await
{ {
Ok(r) => { Ok(r) => {
@ -59,13 +59,15 @@ pub async fn follow_request(
return Json(e.into()); return Json(e.into());
}; };
// award achievement
if let Err(e) = data if let Err(e) = data
.add_achievement(&user, AchievementName::FollowUser.into()) .add_achievement(&mut user, AchievementName::FollowUser.into())
.await .await
{ {
return Json(e.into()); return Json(e.into());
} }
// ...
Json(ApiReturn { Json(ApiReturn {
ok: true, ok: true,
message: "User followed".to_string(), message: "User followed".to_string(),
@ -123,7 +125,7 @@ pub async fn accept_follow_request(
// create follow // create follow
match data match data
.create_userfollow(UserFollow::new(id, user.id), true) .create_userfollow(UserFollow::new(id, user.id), &user, true)
.await .await
{ {
Ok(_) => { Ok(_) => {

View file

@ -37,7 +37,7 @@ pub async fn create_request(
JsonMultipart(images, req): JsonMultipart<CreatePost>, JsonMultipart(images, req): JsonMultipart<CreatePost>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let data = &(data.read().await).0; let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreatePosts) { let mut user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreatePosts) {
Some(ua) => ua, Some(ua) => ua,
None => return Json(Error::NotAllowed.into()), None => return Json(Error::NotAllowed.into()),
}; };
@ -181,7 +181,7 @@ pub async fn create_request(
// achievements // achievements
if let Err(e) = data if let Err(e) = data
.add_achievement(&user, AchievementName::CreatePost.into()) .add_achievement(&mut user, AchievementName::CreatePost.into())
.await .await
{ {
return Json(e.into()); return Json(e.into());
@ -189,7 +189,7 @@ pub async fn create_request(
if user.post_count >= 49 { if user.post_count >= 49 {
if let Err(e) = data if let Err(e) = data
.add_achievement(&user, AchievementName::Create50Posts.into()) .add_achievement(&mut user, AchievementName::Create50Posts.into())
.await .await
{ {
return Json(e.into()); return Json(e.into());
@ -198,7 +198,7 @@ pub async fn create_request(
if user.post_count >= 99 { if user.post_count >= 99 {
if let Err(e) = data if let Err(e) = data
.add_achievement(&user, AchievementName::Create100Posts.into()) .add_achievement(&mut user, AchievementName::Create100Posts.into())
.await .await
{ {
return Json(e.into()); return Json(e.into());
@ -207,7 +207,7 @@ pub async fn create_request(
if user.post_count >= 999 { if user.post_count >= 999 {
if let Err(e) = data if let Err(e) = data
.add_achievement(&user, AchievementName::Create1000Posts.into()) .add_achievement(&mut user, AchievementName::Create1000Posts.into())
.await .await
{ {
return Json(e.into()); return Json(e.into());

View file

@ -52,12 +52,23 @@ pub async fn create_request(
// award achievement // award achievement
if let Some(ref user) = user { if let Some(ref user) = user {
let mut user = user.clone();
if let Err(e) = data if let Err(e) = data
.add_achievement(user, AchievementName::CreateQuestion.into()) .add_achievement(&mut user, AchievementName::CreateQuestion.into())
.await .await
{ {
return Json(e.into()); return Json(e.into());
} }
if drawings.len() > 0 {
if let Err(e) = data
.add_achievement(&mut user, AchievementName::CreateDrawing.into())
.await
{
return Json(e.into());
}
}
} }
// ... // ...

View file

@ -98,7 +98,7 @@ pub async fn create_request(
Json(props): Json<CreateJournal>, Json(props): Json<CreateJournal>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let data = &(data.read().await).0; let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateJournals) { let mut user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateJournals) {
Some(ua) => ua, Some(ua) => ua,
None => return Json(Error::NotAllowed.into()), None => return Json(Error::NotAllowed.into()),
}; };
@ -110,7 +110,7 @@ pub async fn create_request(
Ok(x) => { Ok(x) => {
// award achievement // award achievement
if let Err(e) = data if let Err(e) = data
.add_achievement(&user, AchievementName::CreateJournal.into()) .add_achievement(&mut user, AchievementName::CreateJournal.into())
.await .await
{ {
return Json(e.into()); return Json(e.into());

View file

@ -10,7 +10,10 @@ use axum::{
use axum_extra::extract::CookieJar; use axum_extra::extract::CookieJar;
use serde::Deserialize; use serde::Deserialize;
use tetratto_core::model::{ use tetratto_core::model::{
auth::DefaultTimelineChoice, permissions::FinePermission, requests::ActionType, Error, auth::{AchievementName, DefaultTimelineChoice},
permissions::FinePermission,
requests::ActionType,
Error,
}; };
use std::fs::read_to_string; use std::fs::read_to_string;
use pathbufd::PathBufD; use pathbufd::PathBufD;
@ -447,7 +450,7 @@ pub async fn achievements_request(
Extension(data): Extension<State>, Extension(data): Extension<State>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let data = data.read().await; let data = data.read().await;
let user = match get_user_from_token!(jar, data.0) { let mut user = match get_user_from_token!(jar, data.0) {
Some(ua) => ua, Some(ua) => ua,
None => { None => {
return Err(Html( return Err(Html(
@ -458,6 +461,15 @@ pub async fn achievements_request(
let achievements = data.0.fill_achievements(user.achievements.clone()); let achievements = data.0.fill_achievements(user.achievements.clone());
// award achievement
if let Err(e) = data
.0
.add_achievement(&mut user, AchievementName::OpenAchievements.into())
.await
{
return Err(Html(render_error(e, &jar, &data, &None).await));
}
// ... // ...
let lang = get_lang!(jar, data.0); let lang = get_lang!(jar, data.0);
let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "tetratto-core" name = "tetratto-core"
version = "9.0.0" version = "10.0.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View file

@ -712,7 +712,7 @@ impl DataManager {
/// Add an achievement to a user. /// Add an achievement to a user.
/// ///
/// Still returns `Ok` if the user already has the achievement. /// Still returns `Ok` if the user already has the achievement.
pub async fn add_achievement(&self, user: &User, achievement: Achievement) -> Result<()> { pub async fn add_achievement(&self, user: &mut User, achievement: Achievement) -> Result<()> {
if user if user
.achievements .achievements
.iter() .iter()
@ -734,9 +734,8 @@ impl DataManager {
.await?; .await?;
// add achievement // add achievement
let mut user = user.clone();
user.achievements.push(achievement); user.achievements.push(achievement);
self.update_user_achievements(user.id, user.achievements) self.update_user_achievements(user.id, user.achievements.to_owned())
.await?; .await?;
Ok(()) Ok(())

View file

@ -1,9 +1,9 @@
use oiseau::cache::Cache; use oiseau::cache::Cache;
use crate::model::{ use crate::model::{
Error, Result, auth::{AchievementName, Notification, User},
auth::{Notification, User},
permissions::FinePermission, permissions::FinePermission,
reactions::{AssetType, Reaction}, reactions::{AssetType, Reaction},
Error, Result,
}; };
use crate::{auto_method, DataManager}; use crate::{auto_method, DataManager};
@ -148,6 +148,33 @@ impl DataManager {
{ {
return Err(Error::NotAllowed); return Err(Error::NotAllowed);
} }
// achievements
if user.id != post.owner {
let mut owner = self.get_user_by_id(post.owner).await?;
self.add_achievement(&mut owner, AchievementName::Get1Like.into())
.await?;
if post.likes >= 9 {
self.add_achievement(&mut owner, AchievementName::Get10Likes.into())
.await?;
}
if post.likes >= 49 {
self.add_achievement(&mut owner, AchievementName::Get50Likes.into())
.await?;
}
if post.likes >= 99 {
self.add_achievement(&mut owner, AchievementName::Get100Likes.into())
.await?;
}
if post.dislikes >= 24 {
self.add_achievement(&mut owner, AchievementName::Get25Dislikes.into())
.await?;
}
}
} else if data.asset_type == AssetType::Question { } else if data.asset_type == AssetType::Question {
let question = self.get_question_by_id(data.asset).await?; let question = self.get_question_by_id(data.asset).await?;

View file

@ -1,5 +1,5 @@
use oiseau::cache::Cache; use oiseau::cache::Cache;
use crate::model::auth::FollowResult; use crate::model::auth::{AchievementName, FollowResult};
use crate::model::requests::{ActionRequest, ActionType}; use crate::model::requests::{ActionRequest, ActionType};
use crate::model::{Error, Result, auth::User, auth::UserFollow, permissions::FinePermission}; use crate::model::{Error, Result, auth::User, auth::UserFollow, permissions::FinePermission};
use crate::{auto_method, DataManager}; use crate::{auto_method, DataManager};
@ -238,9 +238,14 @@ impl DataManager {
/// # Arguments /// # Arguments
/// * `data` - a mock [`UserFollow`] object to insert /// * `data` - a mock [`UserFollow`] object to insert
/// * `force` - if we should skip the request stage /// * `force` - if we should skip the request stage
pub async fn create_userfollow(&self, data: UserFollow, force: bool) -> Result<FollowResult> { pub async fn create_userfollow(
&self,
data: UserFollow,
initiator: &User,
force: bool,
) -> Result<FollowResult> {
if !force { if !force {
let other_user = self.get_user_by_id(data.receiver).await?; let mut other_user = self.get_user_by_id(data.receiver).await?;
if other_user.settings.private_profile { if other_user.settings.private_profile {
// send follow request instead // send follow request instead
@ -254,6 +259,12 @@ impl DataManager {
return Ok(FollowResult::Requested); return Ok(FollowResult::Requested);
} }
// check if we're staff
if initiator.permissions.check(FinePermission::STAFF_BADGE) {
self.add_achievement(&mut other_user, AchievementName::FollowedByStaff.into())
.await?;
}
} }
// ... // ...

View file

@ -258,6 +258,9 @@ pub struct UserSettings {
/// Automatically clear all notifications when notifications are viewed. /// Automatically clear all notifications when notifications are viewed.
#[serde(default)] #[serde(default)]
pub auto_clear_notifs: bool, pub auto_clear_notifs: bool,
/// Increase the text size of buttons and paragraphs.
#[serde(default)]
pub large_text: bool,
} }
fn mime_avif() -> String { fn mime_avif() -> String {
@ -475,26 +478,26 @@ pub struct ExternalConnectionData {
} }
/// The total number of achievements needed to 100% Tetratto! /// The total number of achievements needed to 100% Tetratto!
pub const ACHIEVEMENTS: usize = 8; pub const ACHIEVEMENTS: usize = 16;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum AchievementName { pub enum AchievementName {
/// Create your first post.
CreatePost, CreatePost,
/// Follow somebody.
FollowUser, FollowUser,
/// Create your 50th post.
Create50Posts, Create50Posts,
/// Create your 100th post.
Create100Posts, Create100Posts,
/// Create your 1000th post.
Create1000Posts, Create1000Posts,
/// Ask your first question.
CreateQuestion, CreateQuestion,
/// Edit your settings.
EditSettings, EditSettings,
/// Create your first journal.
CreateJournal, CreateJournal,
FollowedByStaff,
CreateDrawing,
OpenAchievements,
Get1Like,
Get10Likes,
Get50Likes,
Get100Likes,
Get25Dislikes,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
@ -515,6 +518,14 @@ impl AchievementName {
Self::CreateQuestion => "Big questions...", Self::CreateQuestion => "Big questions...",
Self::EditSettings => "Just how I like it!", Self::EditSettings => "Just how I like it!",
Self::CreateJournal => "Dear diary...", Self::CreateJournal => "Dear diary...",
Self::FollowedByStaff => "Big Shrimpin'",
Self::CreateDrawing => "Modern art",
Self::OpenAchievements => "Welcome!",
Self::Get1Like => "Baby steps!",
Self::Get10Likes => "WOW! 10 LIKES!",
Self::Get50Likes => "banger post follow for more",
Self::Get100Likes => "everyone liked that",
Self::Get25Dislikes => "Sorry...",
} }
} }
@ -528,19 +539,37 @@ impl AchievementName {
Self::CreateQuestion => "Ask your first question!", Self::CreateQuestion => "Ask your first question!",
Self::EditSettings => "Edit your settings.", Self::EditSettings => "Edit your settings.",
Self::CreateJournal => "Create your first journal.", Self::CreateJournal => "Create your first journal.",
Self::FollowedByStaff => "Get followed by a staff member!",
Self::CreateDrawing => "Include a drawing in a question.",
Self::OpenAchievements => "Open the achievements page.",
Self::Get1Like => "Get 1 like on a post! Good job!",
Self::Get10Likes => "Get 10 likes on one post.",
Self::Get50Likes => "Get 50 likes on one post.",
Self::Get100Likes => "Get 100 likes on one post.",
Self::Get25Dislikes => "Get 25 dislikes on one post... :(",
} }
} }
pub fn rarity(&self) -> AchievementRarity { pub fn rarity(&self) -> AchievementRarity {
// i don't want to write that long ass type name everywhere
use AchievementRarity::*;
match self { match self {
Self::CreatePost => AchievementRarity::Common, Self::CreatePost => Common,
Self::FollowUser => AchievementRarity::Common, Self::FollowUser => Common,
Self::Create50Posts => AchievementRarity::Uncommon, Self::Create50Posts => Uncommon,
Self::Create100Posts => AchievementRarity::Uncommon, Self::Create100Posts => Uncommon,
Self::Create1000Posts => AchievementRarity::Rare, Self::Create1000Posts => Rare,
Self::CreateQuestion => AchievementRarity::Common, Self::CreateQuestion => Common,
Self::EditSettings => AchievementRarity::Common, Self::EditSettings => Common,
Self::CreateJournal => AchievementRarity::Uncommon, Self::CreateJournal => Uncommon,
Self::FollowedByStaff => Rare,
Self::CreateDrawing => Common,
Self::OpenAchievements => Common,
Self::Get1Like => Common,
Self::Get10Likes => Common,
Self::Get50Likes => Uncommon,
Self::Get100Likes => Rare,
Self::Get25Dislikes => Uncommon,
} }
} }
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "tetratto-l10n" name = "tetratto-l10n"
version = "9.0.0" version = "10.0.0"
edition = "2024" edition = "2024"
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true

View file

@ -1,6 +1,6 @@
[package] [package]
name = "tetratto-shared" name = "tetratto-shared"
version = "9.0.0" version = "10.0.0"
edition = "2024" edition = "2024"
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true