From da4d7c7cd1fb1af6b58843ae17f9c608996a8a51 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 14 Dec 2024 22:54:57 +0530 Subject: [PATCH 01/26] feat(migrations): add table for user notification --- crates/migrations/src/lib.rs | 2 + .../m20240531_create_queued_notification.rs | 1 + .../src/m20241214_create_user_notification.rs | 60 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 crates/migrations/src/m20241214_create_user_notification.rs diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index bb7e56261d..2e7f24301d 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -52,6 +52,7 @@ mod m20241121_changes_for_issue_445; mod m20241124_changes_for_issue_1118; mod m20241126_changes_for_issue_1113; mod m20241129_changes_for_issue_1114; +mod m20241214_create_user_notification; pub use m20230410_create_metadata::Metadata as AliasedMetadata; pub use m20230413_create_person::Person as AliasedPerson; @@ -125,6 +126,7 @@ impl MigratorTrait for Migrator { Box::new(m20241124_changes_for_issue_1118::Migration), Box::new(m20241129_changes_for_issue_1114::Migration), Box::new(m20241126_changes_for_issue_1113::Migration), + Box::new(m20241214_create_user_notification::Migration), ] } } diff --git a/crates/migrations/src/m20240531_create_queued_notification.rs b/crates/migrations/src/m20240531_create_queued_notification.rs index 894f3531e6..9a4a9577a7 100644 --- a/crates/migrations/src/m20240531_create_queued_notification.rs +++ b/crates/migrations/src/m20240531_create_queued_notification.rs @@ -1,3 +1,4 @@ +// FIXME: Remove this migration completely use sea_orm_migration::prelude::*; use super::m20230417_create_user::User; diff --git a/crates/migrations/src/m20241214_create_user_notification.rs b/crates/migrations/src/m20241214_create_user_notification.rs new file mode 100644 index 0000000000..94825d0e5f --- /dev/null +++ b/crates/migrations/src/m20241214_create_user_notification.rs @@ -0,0 +1,60 @@ +use sea_orm_migration::prelude::*; + +use super::m20230417_create_user::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[derive(Iden)] +pub enum UserNotification { + Id, + Lot, + Table, + UserId, + Message, +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(UserNotification::Table) + .col( + ColumnDef::new(UserNotification::Id) + .uuid() + .not_null() + .default(PgFunc::gen_random_uuid()) + .primary_key(), + ) + .col(ColumnDef::new(UserNotification::Lot).text().not_null()) + .col(ColumnDef::new(UserNotification::Message).text().not_null()) + .col(ColumnDef::new(UserNotification::UserId).text().not_null()) + .foreign_key( + ForeignKey::create() + .name("notification_to_user_foreign_key") + .from(UserNotification::Table, UserNotification::UserId) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; + manager + .create_index( + Index::create() + .name("notification__user_id__index") + .table(UserNotification::Table) + .col(UserNotification::UserId) + .to_owned(), + ) + .await?; + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +} From 566c364958b023c48954207bb3c8c2ebd8fe26c0 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 14 Dec 2024 23:00:18 +0530 Subject: [PATCH 02/26] feat(models): add new model for user notification --- crates/enums/src/lib.rs | 12 +++++++ .../database/src/daily_user_activity.rs | 17 ++++++++- crates/models/database/src/lib.rs | 1 + crates/models/database/src/user.rs | 22 ++++++++++++ .../models/database/src/user_notification.rs | 35 +++++++++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 crates/models/database/src/user_notification.rs diff --git a/crates/enums/src/lib.rs b/crates/enums/src/lib.rs index 0c9108f032..c4ef6e6a31 100644 --- a/crates/enums/src/lib.rs +++ b/crates/enums/src/lib.rs @@ -91,6 +91,18 @@ pub enum UserLot { Normal, } +#[derive( + Debug, Clone, Copy, PartialEq, Eq, EnumIter, DeriveActiveEnum, Deserialize, Serialize, Enum, +)] +#[sea_orm( + rs_type = "String", + db_type = "String(StringLen::None)", + rename_all = "snake_case" +)] +pub enum UserNotificationLot { + Queued, +} + #[derive( Debug, Clone, diff --git a/crates/models/database/src/daily_user_activity.rs b/crates/models/database/src/daily_user_activity.rs index ca16d3abe7..513c21881a 100644 --- a/crates/models/database/src/daily_user_activity.rs +++ b/crates/models/database/src/daily_user_activity.rs @@ -54,6 +54,21 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::UserId", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/models/database/src/lib.rs b/crates/models/database/src/lib.rs index 127aefdca2..e96caeddee 100644 --- a/crates/models/database/src/lib.rs +++ b/crates/models/database/src/lib.rs @@ -27,6 +27,7 @@ pub mod review; pub mod seen; pub mod user; pub mod user_measurement; +pub mod user_notification; pub mod user_to_entity; pub mod workout; pub mod workout_template; diff --git a/crates/models/database/src/user.rs b/crates/models/database/src/user.rs index 5fdb2def6a..d2e17085eb 100644 --- a/crates/models/database/src/user.rs +++ b/crates/models/database/src/user.rs @@ -37,6 +37,8 @@ pub enum Relation { AccessLink, #[sea_orm(has_many = "super::collection::Entity")] Collection, + #[sea_orm(has_many = "super::daily_user_activity::Entity")] + DailyUserActivity, #[sea_orm(has_many = "super::exercise::Entity")] Exercise, #[sea_orm(has_many = "super::import_report::Entity")] @@ -53,6 +55,8 @@ pub enum Relation { Seen, #[sea_orm(has_many = "super::user_measurement::Entity")] UserMeasurement, + #[sea_orm(has_many = "super::user_notification::Entity")] + UserNotification, #[sea_orm(has_many = "super::user_to_entity::Entity")] UserToEntity, #[sea_orm(has_many = "super::workout::Entity")] @@ -67,6 +71,18 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Collection.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DailyUserActivity.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::Exercise.def() @@ -115,6 +131,12 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::UserNotification.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::UserToEntity.def() diff --git a/crates/models/database/src/user_notification.rs b/crates/models/database/src/user_notification.rs new file mode 100644 index 0000000000..dc46f661f5 --- /dev/null +++ b/crates/models/database/src/user_notification.rs @@ -0,0 +1,35 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1 + +use enums::UserNotificationLot; +use sea_orm::entity::prelude::*; +use uuid::Uuid; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "user_notification")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: Uuid, + pub message: String, + pub user_id: String, + pub lot: UserNotificationLot, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::UserId", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} From 49476681f6ace88df40ad24c833a88ec2bc58a05 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 14 Dec 2024 23:01:43 +0530 Subject: [PATCH 03/26] feat(migrations): add new file for issue 1130 migrations --- crates/migrations/src/lib.rs | 2 ++ .../src/m20241215_changes_for_issue_1130.rs | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 crates/migrations/src/m20241215_changes_for_issue_1130.rs diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index 2e7f24301d..8a1130b2ae 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -53,6 +53,7 @@ mod m20241124_changes_for_issue_1118; mod m20241126_changes_for_issue_1113; mod m20241129_changes_for_issue_1114; mod m20241214_create_user_notification; +mod m20241215_changes_for_issue_1130; pub use m20230410_create_metadata::Metadata as AliasedMetadata; pub use m20230413_create_person::Person as AliasedPerson; @@ -127,6 +128,7 @@ impl MigratorTrait for Migrator { Box::new(m20241129_changes_for_issue_1114::Migration), Box::new(m20241126_changes_for_issue_1113::Migration), Box::new(m20241214_create_user_notification::Migration), + Box::new(m20241215_changes_for_issue_1130::Migration), ] } } diff --git a/crates/migrations/src/m20241215_changes_for_issue_1130.rs b/crates/migrations/src/m20241215_changes_for_issue_1130.rs new file mode 100644 index 0000000000..cb06552ba4 --- /dev/null +++ b/crates/migrations/src/m20241215_changes_for_issue_1130.rs @@ -0,0 +1,20 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + if manager.has_table("queued_notification").await? { + db.execute_unprepared(r#"DROP TABLE "queued_notification""#) + .await?; + } + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +} From bfd66ccb40d1d08b69ac78485a7fee4daf1d5a49 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 14 Dec 2024 23:08:32 +0530 Subject: [PATCH 04/26] refactor(backend): switch to new notification model --- crates/models/database/src/lib.rs | 1 - crates/models/database/src/prelude.rs | 2 +- .../database/src/queued_notification.rs | 45 ------------------- crates/models/database/src/user.rs | 8 ---- crates/services/miscellaneous/src/lib.rs | 32 +++++++------ crates/utils/dependent/src/lib.rs | 16 ++++--- 6 files changed, 29 insertions(+), 75 deletions(-) delete mode 100644 crates/models/database/src/queued_notification.rs diff --git a/crates/models/database/src/lib.rs b/crates/models/database/src/lib.rs index e96caeddee..badc0fb457 100644 --- a/crates/models/database/src/lib.rs +++ b/crates/models/database/src/lib.rs @@ -22,7 +22,6 @@ pub mod metadata_to_person; pub mod monitored_entity; pub mod notification_platform; pub mod person; -pub mod queued_notification; pub mod review; pub mod seen; pub mod user; diff --git a/crates/models/database/src/prelude.rs b/crates/models/database/src/prelude.rs index 6a216d9f56..78836630f1 100644 --- a/crates/models/database/src/prelude.rs +++ b/crates/models/database/src/prelude.rs @@ -19,11 +19,11 @@ pub use super::metadata_to_person::Entity as MetadataToPerson; pub use super::monitored_entity::Entity as MonitoredEntity; pub use super::notification_platform::Entity as NotificationPlatform; pub use super::person::Entity as Person; -pub use super::queued_notification::Entity as QueuedNotification; pub use super::review::Entity as Review; pub use super::seen::Entity as Seen; pub use super::user::Entity as User; pub use super::user_measurement::Entity as UserMeasurement; +pub use super::user_notification::Entity as UserNotification; pub use super::user_to_entity::Entity as UserToEntity; pub use super::workout::Entity as Workout; pub use super::workout_template::Entity as WorkoutTemplate; diff --git a/crates/models/database/src/queued_notification.rs b/crates/models/database/src/queued_notification.rs deleted file mode 100644 index 2fda4c4dd0..0000000000 --- a/crates/models/database/src/queued_notification.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 - -use async_trait::async_trait; -use nanoid::nanoid; -use sea_orm::{entity::prelude::*, ActiveValue}; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "queued_notification")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub message: String, - pub user_id: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::UserId", - to = "super::user::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - User, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -#[async_trait] -impl ActiveModelBehavior for ActiveModel { - async fn before_save(mut self, _db: &C, insert: bool) -> Result - where - C: ConnectionTrait, - { - if insert { - self.id = ActiveValue::Set(format!("qun_{}", nanoid!(12))); - } - Ok(self) - } -} diff --git a/crates/models/database/src/user.rs b/crates/models/database/src/user.rs index d2e17085eb..d02e7e6559 100644 --- a/crates/models/database/src/user.rs +++ b/crates/models/database/src/user.rs @@ -47,8 +47,6 @@ pub enum Relation { Integration, #[sea_orm(has_many = "super::notification_platform::Entity")] NotificationPlatform, - #[sea_orm(has_many = "super::queued_notification::Entity")] - QueuedNotification, #[sea_orm(has_many = "super::review::Entity")] Review, #[sea_orm(has_many = "super::seen::Entity")] @@ -107,12 +105,6 @@ impl Related for Entity { } } -impl Related for Entity { - fn to() -> RelationDef { - Relation::QueuedNotification.def() - } -} - impl Related for Entity { fn to() -> RelationDef { Relation::Review.def() diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs index eb69c734fb..9ac83a0b2d 100644 --- a/crates/services/miscellaneous/src/lib.rs +++ b/crates/services/miscellaneous/src/lib.rs @@ -31,9 +31,9 @@ use database_models::{ AccessLink, ApplicationCache, CalendarEvent, Collection, CollectionToEntity, Exercise, Genre, ImportReport, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata, MetadataToMetadataGroup, MetadataToPerson, MonitoredEntity, NotificationPlatform, Person, - QueuedNotification, Review, Seen, User, UserToEntity, + Review, Seen, User, UserNotification, UserToEntity, }, - queued_notification, review, seen, user, user_to_entity, + review, seen, user, user_notification, user_to_entity, }; use database_utils::{ apply_collection_filter, calculate_user_activities_and_summary, entity_in_collections, @@ -48,19 +48,19 @@ use dependent_models::{ }; use dependent_utils::{ add_entity_to_collection, commit_metadata, commit_metadata_group_internal, - commit_metadata_internal, commit_person, create_partial_metadata, + commit_metadata_internal, commit_person, create_partial_metadata, create_user_notification, deploy_after_handle_media_seen_tasks, deploy_background_job, deploy_update_metadata_job, first_metadata_image_as_url, get_entity_recently_consumed, get_metadata_provider, get_openlibrary_service, get_tmdb_non_media_service, get_users_and_cte_monitoring_entity, get_users_monitoring_entity, handle_after_media_seen_tasks, is_metadata_finished_by_user, metadata_images_as_urls, post_review, progress_update, - queue_media_state_changed_notification_for_user, queue_notifications_to_user_platforms, - refresh_collection_to_entity_association, update_metadata_and_notify_users, + queue_media_state_changed_notification_for_user, refresh_collection_to_entity_association, + update_metadata_and_notify_users, }; use enums::{ EntityLot, ExerciseEquipment, ExerciseForce, ExerciseLevel, ExerciseLot, ExerciseMechanic, ExerciseMuscle, MediaLot, MediaSource, MetadataToMetadataRelation, SeenState, - UserToMediaReason, + UserNotificationLot, UserToMediaReason, }; use env_utils::{APP_VERSION, UNKEY_API_ID}; use futures::TryStreamExt; @@ -2430,10 +2430,11 @@ ORDER BY RANDOM() LIMIT 10; let related_users = col.find_related(UserToEntity).all(&self.0.db).await?; if get_current_date(&self.0.timezone) == reminder.reminder { for user in related_users { - queue_notifications_to_user_platforms( - &user.user_id, + create_user_notification( &reminder.text, + &user.user_id, &self.0.db, + UserNotificationLot::Queued, ) .await?; remove_entity_from_collection( @@ -2838,13 +2839,14 @@ ORDER BY RANDOM() LIMIT 10; event.entity_lot, Some("reviews"), ); - queue_notifications_to_user_platforms( - &user_id, + create_user_notification( &format!( "New review posted for {} ({}, {}) by {}.", event.obj_title, event.entity_lot, url, event.username ), + &user_id, &self.0.db, + UserNotificationLot::Queued, ) .await?; } @@ -2978,7 +2980,10 @@ ORDER BY RANDOM() LIMIT 10; Genre::delete_by_id(genre).exec(&self.0.db).await?; } ryot_log!(debug, "Deleting all queued notifications"); - QueuedNotification::delete_many().exec(&self.0.db).await?; + UserNotification::delete_many() + .filter(user_notification::Column::Lot.eq(UserNotificationLot::Queued)) + .exec(&self.0.db) + .await?; ryot_log!(debug, "Deleting revoked access tokens"); AccessLink::delete_many() .filter(access_link::Column::IsRevoked.eq(true)) @@ -3055,8 +3060,9 @@ ORDER BY RANDOM() LIMIT 10; let users = User::find().all(&self.0.db).await?; for user_details in users { ryot_log!(debug, "Sending notification to user: {:?}", user_details.id); - let notifications = QueuedNotification::find() - .filter(queued_notification::Column::UserId.eq(&user_details.id)) + let notifications = UserNotification::find() + .filter(user_notification::Column::UserId.eq(&user_details.id)) + .filter(user_notification::Column::Lot.eq(UserNotificationLot::Queued)) .all(&self.0.db) .await?; if notifications.is_empty() { diff --git a/crates/utils/dependent/src/lib.rs b/crates/utils/dependent/src/lib.rs index 575bab68b9..291e013e66 100644 --- a/crates/utils/dependent/src/lib.rs +++ b/crates/utils/dependent/src/lib.rs @@ -18,7 +18,7 @@ use database_models::{ Collection, CollectionToEntity, Exercise, Genre, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata, MetadataToPerson, MonitoredEntity, Person, Seen, UserToEntity, Workout, }, - queued_notification, review, seen, user_measurement, user_to_entity, workout, + review, seen, user_measurement, user_notification, user_to_entity, workout, }; use database_utils::{ admin_account_guard, create_or_update_collection, get_cte_column_from_lot, @@ -27,7 +27,7 @@ use database_utils::{ use dependent_models::{ImportCompletedItem, ImportResult}; use enums::{ EntityLot, ExerciseLot, ExerciseSource, MediaLot, MediaSource, MetadataToMetadataRelation, - SeenState, Visibility, WorkoutSetPersonalBest, + SeenState, UserNotificationLot, Visibility, WorkoutSetPersonalBest, }; use file_storage_service::FileStorageService; use fitness_models::{ @@ -673,13 +673,15 @@ pub async fn get_users_monitoring_entity( ) } -pub async fn queue_notifications_to_user_platforms( +pub async fn create_user_notification( + message: &str, user_id: &String, - msg: &str, db: &DatabaseConnection, + lot: UserNotificationLot, ) -> Result { - let insert_data = queued_notification::ActiveModel { - message: ActiveValue::Set(msg.to_owned()), + let insert_data = user_notification::ActiveModel { + lot: ActiveValue::Set(lot), + message: ActiveValue::Set(message.to_owned()), user_id: ActiveValue::Set(user_id.to_owned()), ..Default::default() }; @@ -696,7 +698,7 @@ pub async fn queue_media_state_changed_notification_for_user( let (msg, change) = notification; let notification_preferences = user_by_id(user_id, ss).await?.preferences.notifications; if notification_preferences.enabled && notification_preferences.to_send.contains(change) { - queue_notifications_to_user_platforms(user_id, msg, &ss.db) + create_user_notification(msg, user_id, &ss.db, UserNotificationLot::Queued) .await .trace_ok(); } else { From fc339e06023d873ab40259cc397a1a352af993a5 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 14 Dec 2024 23:26:10 +0530 Subject: [PATCH 05/26] refactor(frontend): extract component to add images to workout --- .../app/routes/_dashboard.fitness.$action.tsx | 235 ++++++++++-------- 1 file changed, 134 insertions(+), 101 deletions(-) diff --git a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx index 5fb964bbc3..aad2e7b365 100644 --- a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx @@ -217,6 +217,9 @@ export default function Page() { ] = useDisclosure(false); const [_, setMeasurementsDrawerOpen] = useMeasurementsDrawerOpen(); const [currentTimer, setCurrentTimer] = useTimerAtom(); + const [assetsModalOpened, setAssetsModalOpened] = useState< + string | null | undefined + >(undefined); useInterval(() => { const timeRemaining = dayjsLib(currentTimer?.endAt).diff( @@ -266,6 +269,10 @@ export default function Page() { Loading workout...}> {() => ( <> + setAssetsModalOpened(undefined)} + /> setSupersetModalOpened(s)} + setOpenAssetsModal={() => + setAssetsModalOpened(ex.identifier) + } /> ))} @@ -937,6 +947,127 @@ const exerciseHasDetailsToShow = ( (details?.attributes.images.length || 0) > 0 || (userDetails?.history?.length || 0) > 0; +const UploadAssetsModal = (props: { + closeModal: () => void; + modalOpenedBy: string | null | undefined; +}) => { + const coreDetails = useCoreDetails(); + const fileUploadAllowed = coreDetails.fileStorageEnabled; + const [currentWorkout, setCurrentWorkout] = useCurrentWorkout(); + const [cameraFacing, toggleCameraFacing] = useToggle([ + "environment", + "user", + ] as const); + const webcamRef = useRef(null); + + if (!currentWorkout) return null; + + const exerciseIdx = currentWorkout.exercises.findIndex( + (e) => e.identifier === props.modalOpenedBy, + ); + const exercise = + exerciseIdx !== -1 ? currentWorkout.exercises[exerciseIdx] : null; + + return ( + props.closeModal()} + opened={props.modalOpenedBy !== undefined} + > + + + Images for {exercise ? exercise.name : "the workout"} + + {fileUploadAllowed ? ( + <> + {exercise && + isString(props.modalOpenedBy) && + exercise.images.length > 0 ? ( + + {exercise.images.map((i, imgIdx) => ( + { + deleteUploadedAsset(i.key); + setCurrentWorkout( + produce(currentWorkout, (draft) => { + const images = draft.exercises[exerciseIdx].images; + images.splice(imgIdx, 1); + draft.exercises[exerciseIdx].images = images; + }), + ); + }} + /> + ))} + + ) : null} + + + + + + toggleCameraFacing()}> + + + { + const imageSrc = webcamRef.current?.getScreenshot(); + if (imageSrc) { + const buffer = Buffer.from( + imageSrc.replace(/^data:image\/\w+;base64,/, ""), + "base64", + ); + const fileObj = new File([buffer], "image.jpg", { + type: fileType, + }); + const toSubmitForm = new FormData(); + toSubmitForm.append("file", fileObj, "image.jpg"); + const resp = await fetch( + $path("/actions", { intent: "uploadWorkoutAsset" }), + { method: "POST", body: toSubmitForm }, + ); + const data = await resp.json(); + setCurrentWorkout( + produce(currentWorkout, (draft) => { + draft.exercises[exerciseIdx].images.push({ + imageSrc, + key: data.key, + }); + }), + ); + } + }} + > + + + + + + + ) : ( + + Please set the S3 variables required to enable file uploading + + )} + + + ); +}; + const ExerciseDisplay = (props: { exerciseIdx: number; stopTimer: () => void; @@ -944,6 +1075,7 @@ const ExerciseDisplay = (props: { openTimerDrawer: () => void; reorderDrawerToggle: () => void; openSupersetModal: (s: string) => void; + setOpenAssetsModal: (identifier: string) => void; }) => { const { isCreatingTemplate } = useLoaderData(); const theme = useMantineTheme(); @@ -956,7 +1088,6 @@ const ExerciseDisplay = (props: { const exercise = useGetExerciseAtIndex(props.exerciseIdx); invariant(exercise); const coreDetails = useCoreDetails(); - const fileUploadAllowed = coreDetails.fileStorageEnabled; const [detailsParent] = useAutoAnimate(); const { data: exerciseDetails } = useQuery( getExerciseDetailsQuery(exercise.exerciseId), @@ -970,15 +1101,6 @@ const ExerciseDisplay = (props: { const sound = new Howl({ src: ["/add-set.mp3"] }); if (!userPreferences.fitness.logging.muteSounds) sound.play(); }; - const [cameraFacing, toggleCameraFacing] = useToggle([ - "environment", - "user", - ] as const); - const webcamRef = useRef(null); - const [ - assetsModalOpened, - { close: assetsModalClose, toggle: assetsModalToggle }, - ] = useDisclosure(false); const exerciseHistory = userExerciseDetails?.history; const [durationCol, distanceCol, weightCol, repsCol] = match(exercise.lot) @@ -1013,95 +1135,6 @@ const ExerciseDisplay = (props: { return ( <> - - - Images for {exercise.name} - {fileUploadAllowed ? ( - <> - {exercise.images.length > 0 ? ( - - {exercise.images.map((i, imgIdx) => ( - { - deleteUploadedAsset(i.key); - setCurrentWorkout( - produce(currentWorkout, (draft) => { - const images = - draft.exercises[props.exerciseIdx].images; - images.splice(imgIdx, 1); - draft.exercises[props.exerciseIdx].images = images; - }), - ); - }} - /> - ))} - - ) : null} - - - - - - toggleCameraFacing()}> - - - { - const imageSrc = webcamRef.current?.getScreenshot(); - if (imageSrc) { - const buffer = Buffer.from( - imageSrc.replace(/^data:image\/\w+;base64,/, ""), - "base64", - ); - const fileObj = new File([buffer], "image.jpg", { - type: fileType, - }); - const toSubmitForm = new FormData(); - toSubmitForm.append("file", fileObj, "image.jpg"); - const resp = await fetch( - $path("/actions", { intent: "uploadWorkoutAsset" }), - { method: "POST", body: toSubmitForm }, - ); - const data = await resp.json(); - setCurrentWorkout( - produce(currentWorkout, (draft) => { - draft.exercises[props.exerciseIdx].images.push({ - imageSrc, - key: data.key, - }); - }), - ); - } - }} - > - - - - - - - ) : ( - - Please set the S3 variables required to enable file uploading - - )} - - } - onClick={assetsModalToggle} + style={isCreatingTemplate ? { display: "none" } : undefined} + onClick={() => props.setOpenAssetsModal(exercise.identifier)} rightSection={ exercise.images.length > 0 ? exercise.images.length : null } - style={isCreatingTemplate ? { display: "none" } : undefined} > Images From 3bc5b05088cd59e88e5133ce5d4128eef7cc245d Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 14 Dec 2024 23:41:09 +0530 Subject: [PATCH 06/26] feat(frontend): allow uploading images for workout --- apps/frontend/app/lib/state/fitness.ts | 4 +- .../app/routes/_dashboard.fitness.$action.tsx | 54 ++++++++++++++----- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/apps/frontend/app/lib/state/fitness.ts b/apps/frontend/app/lib/state/fitness.ts index 74e88e9acd..54b7f91889 100644 --- a/apps/frontend/app/lib/state/fitness.ts +++ b/apps/frontend/app/lib/state/fitness.ts @@ -78,7 +78,7 @@ export type InProgressWorkout = { videos: Array; repeatedFrom?: string; supersets: Superset[]; - images: Array; + images: Array; updateWorkoutId?: string; exercises: Array; replacingExerciseIdx?: number; @@ -209,8 +209,8 @@ export const currentWorkoutToCreateWorkoutInput = ( updateWorkoutTemplateId: currentWorkout.updateWorkoutTemplateId, startTime: new Date(currentWorkout.startTime).toISOString(), assets: { - images: [...currentWorkout.images], videos: [...currentWorkout.videos], + images: currentWorkout.images.map((m) => m.key), }, }, }; diff --git a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx index aad2e7b365..de2efeadb9 100644 --- a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx @@ -289,7 +289,9 @@ export default function Page() { onClose={() => setSupersetModalOpened(null)} /> - + setAssetsModalOpened(null)} + /> { +const NameAndCommentInputs = (props: { + openAssetsModal: () => void; +}) => { const [currentWorkout, setCurrentWorkout] = useCurrentWorkout(); invariant(currentWorkout); @@ -573,12 +577,17 @@ const NameAndCommentInputs = () => { return ( <> setName(e.currentTarget.value)} + rightSection={ + + + + } />