@@ -48,19 +48,31 @@ struct FeedbackRequestBody<'a> {
token: &'a str,
}
-#[derive(PartialEq)]
-enum FeedbackModalState {
+#[derive(Debug, Clone, PartialEq)]
+enum InvalidStateIssue {
+ EmailAddress,
+ CharacterCount,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+enum CannotSubmitReason {
+ InvalidState { issues: Vec<InvalidStateIssue> },
+ AwaitingSubmission,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+enum SubmissionState {
CanSubmit,
- DismissModal,
- AwaitingSubmissionResponse,
+ CannotSubmit { reason: CannotSubmitReason },
}
pub struct FeedbackModal {
system_specs: SystemSpecs,
feedback_editor: View<Editor>,
email_address_editor: View<Editor>,
+ submission_state: Option<SubmissionState>,
+ dismiss_modal: bool,
character_count: i32,
- state: FeedbackModalState,
}
impl FocusableView for FeedbackModal {
@@ -72,7 +84,7 @@ impl EventEmitter<DismissEvent> for FeedbackModal {}
impl ModalView for FeedbackModal {
fn on_before_dismiss(&mut self, cx: &mut ViewContext<Self>) -> bool {
- if self.state == FeedbackModalState::DismissModal {
+ if self.dismiss_modal {
return true;
}
@@ -86,7 +98,7 @@ impl ModalView for FeedbackModal {
cx.spawn(move |this, mut cx| async move {
if answer.await.ok() == Some(0) {
this.update(&mut cx, |this, cx| {
- this.state = FeedbackModalState::DismissModal;
+ this.dismiss_modal = true;
cx.emit(DismissEvent)
})
.log_err();
@@ -179,7 +191,8 @@ impl FeedbackModal {
system_specs: system_specs.clone(),
feedback_editor,
email_address_editor,
- state: FeedbackModalState::CanSubmit,
+ submission_state: None,
+ dismiss_modal: false,
character_count: 0,
}
}
@@ -214,7 +227,9 @@ impl FeedbackModal {
};
this.update(&mut cx, |this, cx| {
- this.state = FeedbackModalState::AwaitingSubmissionResponse;
+ this.submission_state = Some(SubmissionState::CannotSubmit {
+ reason: CannotSubmitReason::AwaitingSubmission,
+ });
cx.notify();
})
.log_err();
@@ -225,7 +240,7 @@ impl FeedbackModal {
match res {
Ok(_) => {
this.update(&mut cx, |this, cx| {
- this.state = FeedbackModalState::DismissModal;
+ this.dismiss_modal = true;
cx.notify();
cx.emit(DismissEvent)
})
@@ -244,7 +259,7 @@ impl FeedbackModal {
})
.detach();
- this.state = FeedbackModalState::CanSubmit;
+ this.submission_state = Some(SubmissionState::CanSubmit);
cx.notify();
})
.log_err();
@@ -302,29 +317,79 @@ impl FeedbackModal {
Ok(())
}
- fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
- cx.emit(DismissEvent)
- }
-}
+ fn update_submission_state(&mut self, cx: &mut ViewContext<Self>) {
+ if self.awaiting_submission() {
+ return;
+ }
-impl Render for FeedbackModal {
- type Element = Div;
+ let mut invalid_state_issues = Vec::new();
- fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let valid_email_address = match self.email_address_editor.read(cx).text_option(cx) {
Some(email_address) => Regex::new(EMAIL_REGEX).unwrap().is_match(&email_address),
None => true,
};
- let valid_character_count = FEEDBACK_CHAR_LIMIT.contains(&self.character_count);
+ if !valid_email_address {
+ invalid_state_issues.push(InvalidStateIssue::EmailAddress);
+ }
+
+ if !FEEDBACK_CHAR_LIMIT.contains(&self.character_count) {
+ invalid_state_issues.push(InvalidStateIssue::CharacterCount);
+ }
+
+ if invalid_state_issues.is_empty() {
+ self.submission_state = Some(SubmissionState::CanSubmit);
+ } else {
+ self.submission_state = Some(SubmissionState::CannotSubmit {
+ reason: CannotSubmitReason::InvalidState {
+ issues: invalid_state_issues,
+ },
+ });
+ }
+ }
+
+ fn valid_email_address(&self) -> bool {
+ !self.in_invalid_state(InvalidStateIssue::EmailAddress)
+ }
- let awaiting_submission_response =
- self.state == FeedbackModalState::AwaitingSubmissionResponse;
+ fn valid_character_count(&self) -> bool {
+ !self.in_invalid_state(InvalidStateIssue::CharacterCount)
+ }
- let allow_submission =
- valid_character_count && valid_email_address && !awaiting_submission_response;
+ fn in_invalid_state(&self, a: InvalidStateIssue) -> bool {
+ match self.submission_state {
+ Some(SubmissionState::CannotSubmit {
+ reason: CannotSubmitReason::InvalidState { ref issues },
+ }) => issues.contains(&a),
+ _ => false,
+ }
+ }
- let submit_button_text = if awaiting_submission_response {
+ fn awaiting_submission(&self) -> bool {
+ matches!(
+ self.submission_state,
+ Some(SubmissionState::CannotSubmit {
+ reason: CannotSubmitReason::AwaitingSubmission
+ })
+ )
+ }
+
+ fn can_submit(&self) -> bool {
+ matches!(self.submission_state, Some(SubmissionState::CanSubmit))
+ }
+
+ fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+ cx.emit(DismissEvent)
+ }
+}
+
+impl Render for FeedbackModal {
+ type Element = Div;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ self.update_submission_state(cx);
+
+ let submit_button_text = if self.awaiting_submission() {
"Submitting..."
} else {
"Submit"
@@ -362,7 +427,7 @@ impl Render for FeedbackModal {
*FEEDBACK_CHAR_LIMIT.end() - self.character_count
)
})
- .color(if valid_character_count {
+ .color(if self.valid_character_count() {
Color::Success
} else {
Color::Error
@@ -386,7 +451,7 @@ impl Render for FeedbackModal {
.p_2()
.border()
.rounded_md()
- .border_color(if valid_email_address {
+ .border_color(if self.valid_email_address() {
cx.theme().colors().border
} else {
red()
@@ -423,7 +488,7 @@ impl Render for FeedbackModal {
})),
)
.child(
- Button::new("send_feedback", submit_button_text)
+ Button::new("submit_feedback", submit_button_text)
.color(Color::Accent)
.style(ButtonStyle::Filled)
.on_click(cx.listener(|this, _, cx| {
@@ -437,7 +502,7 @@ impl Render for FeedbackModal {
cx,
)
})
- .when(!allow_submission, |this| this.disabled(true)),
+ .when(!self.can_submit(), |this| this.disabled(true)),
),
),
),
@@ -447,3 +512,42 @@ impl Render for FeedbackModal {
// TODO: Maybe store email address whenever the modal is closed, versus just on submit, so users can remove it if they want without submitting
// TODO: Testing of various button states, dismissal prompts, etc.
+
+// #[cfg(test)]
+// mod test {
+// use super::*;
+
+// #[test]
+// fn test_invalid_email_addresses() {
+// let markdown = markdown.await.log_err();
+// let buffer = project.update(&mut cx, |project, cx| {
+// project.create_buffer("", markdown, cx)
+// })??;
+
+// workspace.update(&mut cx, |workspace, cx| {
+// let system_specs = SystemSpecs::new(cx);
+
+// workspace.toggle_modal(cx, move |cx| {
+// let feedback_modal = FeedbackModal::new(system_specs, project, buffer, cx);
+
+// assert!(!feedback_modal.can_submit());
+// assert!(!feedback_modal.valid_email_address(cx));
+// assert!(!feedback_modal.valid_character_count());
+
+// feedback_modal
+// .email_address_editor
+// .update(cx, |this, cx| this.set_text("a", cx));
+// feedback_modal.set_submission_state(cx);
+
+// assert!(!feedback_modal.valid_email_address(cx));
+
+// feedback_modal
+// .email_address_editor
+// .update(cx, |this, cx| this.set_text("a&b.com", cx));
+// feedback_modal.set_submission_state(cx);
+
+// assert!(feedback_modal.valid_email_address(cx));
+// });
+// })?;
+// }
+// }