Conversation
…into image_upload_2.0
|
Hi @alanpoon, sorry just getting around to reviewing things after my bonanza of makepad fixes. Is this PR ready to merge in? (just wondering since you hadn't yet requested a review from me or assigned the if so, lmk and i'll review it asap. |
|
hi @alanpoon, friendly bump on this. Is this ready? (aside from the conflicts) No real rush, but i'm planning to do an alpha release soon and it'd be great to have this feature included! Plus I'd hate to see all your hard work go to waste. |
|
Working on it. |
kevinaboos
left a comment
There was a problem hiding this comment.
Thanks Alan, i appreciate the contribution here, especially updating it for Makepad 2.0.
I left a few initial comments about the major issues that stuck out to me. I may add more in the future, but these are a good set of requests for you to get started on.
| if let UploadProgressViewAction::Retry { file_data, room_id } = action.as_widget_action().cast() { | ||
| let Some(tl) = self.tl_state.as_ref() else { continue }; | ||
| // Only handle if this action is for the current room. | ||
| if tl.kind.room_id() != &room_id { continue }; |
There was a problem hiding this comment.
Here we need to use TimelineKind instead of just room ID, to support uploading an image/file to a thread, not just a room.
| // Send the file upload request to the currently selected room | ||
| if let Some(selected_room) = &self.app_state.selected_room { | ||
| if let Some(timeline_kind) = selected_room.timeline_kind() { | ||
| if let Some(sender) = get_timeline_update_sender(&timeline_kind) { |
There was a problem hiding this comment.
this accesses backend data (in sliding_sync) from the frontend (main UI thread), which is behind a lock. That's not a great design, and violates separation of concerns.
Instead of just sending the upload to the currently-selected room (which requires adding this undesirable get_timeline_update_sender() function), you can just include the TimelineKind and timeline update sender in this action directly. Or, even better yet, just store the TimelineKind and timeline update sender in the modal?
To be honest, I"m not sure why this code is here at all. Shouldn't the modal itself handle the UploadConfirmed action directly? In the top-level App, we only want to handle the bare minimum of action variants, i.e., just Show and Hide in this case.
| /// Returns a clone of the timeline update sender for the given timeline. | ||
| /// | ||
| /// This can be called multiple times, as it only clones the sender. | ||
| pub fn get_timeline_update_sender(kind: &TimelineKind) -> Option<crossbeam_channel::Sender<TimelineUpdate>> { | ||
| let all_joined_rooms = ALL_JOINED_ROOMS.lock().unwrap(); | ||
| let jrd = all_joined_rooms.get(kind.room_id())?; | ||
| let details = match kind { | ||
| TimelineKind::MainRoom { .. } => &jrd.main_timeline, | ||
| TimelineKind::Thread { thread_root_event_id, .. } => jrd.thread_timelines.get(thread_root_event_id)?, | ||
| }; | ||
| Some(details.timeline_update_sender.clone()) | ||
| } |
There was a problem hiding this comment.
this is a code smell, and shouldn't be necessary. Please remove it; see my other comment where it is called.
| /// Returns the `TimelineKind` for this selected room. | ||
| /// | ||
| /// Returns `None` for `InvitedRoom` and `Space` variants, as they don't have timelines. | ||
| pub fn timeline_kind(&self) -> Option<crate::sliding_sync::TimelineKind> { | ||
| match self { | ||
| SelectedRoom::JoinedRoom { room_name_id } => { | ||
| Some(crate::sliding_sync::TimelineKind::MainRoom { | ||
| room_id: room_name_id.room_id().clone(), | ||
| }) | ||
| } | ||
| SelectedRoom::Thread { room_name_id, thread_root_event_id } => { | ||
| Some(crate::sliding_sync::TimelineKind::Thread { | ||
| room_id: room_name_id.room_id().clone(), | ||
| thread_root_event_id: thread_root_event_id.clone(), | ||
| }) | ||
| } | ||
| SelectedRoom::InvitedRoom { .. } | SelectedRoom::Space { .. } => None, | ||
| } | ||
| } |
There was a problem hiding this comment.
you also don't need this function. The entire point of the TimelineKind type is that it replaces room_id as a identifier for a main room or a thread. See my other comment near where this function is called.
| content +: { | ||
| width: Fit, height: Fit, | ||
| align: Align{x: 0.5, y: 0.5}, | ||
| file_upload_modal_inner := FileUploadModal {} | ||
| } |
There was a problem hiding this comment.
this should follow the design of the EventSourceModal, which uses height: Fill and width: Fill for the content here, and then the actual EventSourceModal widget itself defines these layout properties:
width: Fill { max: 1000 }
height: Fill
margin: 40,
align: Align{x: 0.5, y: 0}
padding: Inset{top: 20, right: 25, bottom: 20, left: 25}
flow: Down| width: 400, | ||
| height: Fit, | ||
| flow: Down, | ||
| padding: 20, | ||
| spacing: 15, |
There was a problem hiding this comment.
see my other comment about the proper layout/size and design of this modal -- it should imitate EventSourceModal.
|
I had Claude max do a thorough review, just out of curiosity. I was aware of some of these things and think they can be left for later, but some of them do need to be addressed now. You don't have to fully implement replies, though I think it's basically just a one-line fix, so you might as well since it's quite trivial. But you definitely need to address points 1-5 below, as well as 6 and 7. Note that point 3, 6, 7 are all things i brought up already, related to the TimelineKind usage. Points 9, 10, 12, 13, 14 are also pretty important to address. 🚨 Critical — real behavior bugs1. Replies are silently dropped on every attachment send. // For now, we'll just send the attachment without reply support
// TODO: Add proper reply support for attachments
let _ = replied_to; // Suppress unused warning for now
Worse: 2. Cancel button does nothing to actually abort the upload. let _send_attachment_task = Handle::current().spawn(async move { ... });The Fix: grab 3. Attachment can be sent to the WRONG room. Some(FilePreviewerAction::UploadConfirmed(file_data)) => {
if let Some(selected_room) = &self.app_state.selected_room {
if let Some(timeline_kind) = selected_room.timeline_kind() {
if let Some(sender) = get_timeline_update_sender(&timeline_kind) {
let _ = sender.send(TimelineUpdate::FileUploadConfirmed(file_data.clone()));The target room is resolved from
Result: the file uploads to Room B silently. This is the most dangerous issue in the PR — user data goes to the wrong conversation. 4. if let Some(selected_file_path) = dialog.pick_file() {
5. Progress updates race with completion/error and can overwrite the error UI. Two independent tasks write to the same
No ordering between them. A late 🔴 High — architectural6.
UI modal → Half of this indirection exists only to fetch 7. Already noted in the first review. Related: the RoomScreen retry guard 🟠 Medium — correctness8. No file-size limit. A user picks a 4 GB video. The entire file is 9. The generated thumbnail is dead code.
Lanczos3 resize on a large image is CPU-expensive and currently wasted. Either wire the thumbnail into 10. 11. 12. No duplicate upload prevention. While a file is being read in the background thread, clicking the attach button again overwrites 13. 14. 🟡 Low — code quality15. 16. 17. Double 18. 19. Debug 20. ✅ Things that look fine
Suggested priorityBlock on #1–#6. Those six are user-visible data-safety, liveness, or correctness bugs. #7 is a natural companion to #6 and they're best fixed together with the routing redesign. |
|
Also, if you're busy with other things, I can have Claude take a stab at fixing these things, they're not too complicated so it ought to be doable. Just let me know. |
| const KB: u64 = 1024; | ||
| const MB: u64 = KB * 1024; | ||
| const GB: u64 = MB * 1024; | ||
|
|
||
| if bytes >= GB { | ||
| format!("{:.1} GB", bytes as f64 / GB as f64) | ||
| } else if bytes >= MB { | ||
| format!("{:.1} MB", bytes as f64 / MB as f64) | ||
| } else if bytes >= KB { | ||
| format!("{:.1} KB", bytes as f64 / KB as f64) | ||
| } else { | ||
| format!("{} B", bytes) | ||
| } |
There was a problem hiding this comment.
i think this might be slightly wrong, too. Wouldn't it always result in "X.0" instead of something like X.3?
regardless, Robrix already uses the bytesize crate, so you can just remove this function and replace the callers of this function with:
ByteSize::b(file_size).to_string()
Migration to 2.0 #674