-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
73 changed files
with
6,749 additions
and
1,130 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Ngurra | ||
|
||
Ngurra is a Rust project that aims to clone the popular flashcard application Anki. It is a hobby project created to practice Rust programming language and explore the development of flashcard applications. | ||
|
||
 | ||
|
||
## Features | ||
|
||
- Deck management: Create and delete flashcard decks. | ||
- Flashcard management: Add, flashcards within decks. | ||
- Learn flashcards: Study flashcards using various learning modes. | ||
|
||
## Installation | ||
|
||
To use Anki-rs, you need to have Rust installed on your system. If you don't have Rust installed, you can get it from the official Rust website: [https://www.rust-lang.org/](https://www.rust-lang.org/) | ||
|
||
Once you have Rust installed, you can clone the Anki-rs repository: | ||
|
||
### Running | ||
|
||
```bash | ||
cargo run | ||
``` | ||
|
||
## Credit | ||
|
||
Shout out to these open sources: | ||
|
||
- Anki | ||
- GPUI: Zed's UI framework | ||
- Loungy: The Rust launcher in the vein of Spotlight, Alfred, Raycast. | ||
- Catppuccin: The theme that makes everything look good |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/// Defines unit structs that can be used as actions. | ||
/// To use more complex data types as actions, use `impl_actions!` | ||
#[macro_export] | ||
macro_rules! actions { | ||
($namespace:path, [ $($name:ident),* $(,)? ]) => { | ||
$( | ||
/// The `$name` action see [`gpui::actions!`] | ||
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)] | ||
#[serde(crate = "gpui::private::serde")] | ||
pub struct $name; | ||
|
||
gpui::__impl_action!($namespace, $name, | ||
fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> { | ||
Ok(Box::new(Self)) | ||
} | ||
); | ||
|
||
gpui::register_action!($name); | ||
)* | ||
}; | ||
} | ||
|
||
#[doc(hidden)] | ||
#[macro_export] | ||
macro_rules! __impl_action { | ||
($namespace:path, $name:ident, $build:item) => { | ||
impl gpui::Action for $name { | ||
fn name(&self) -> &'static str | ||
{ | ||
concat!( | ||
stringify!($namespace), | ||
"::", | ||
stringify!($name), | ||
) | ||
} | ||
|
||
fn debug_name() -> &'static str | ||
where | ||
Self: ::std::marker::Sized | ||
{ | ||
concat!( | ||
stringify!($namespace), | ||
"::", | ||
stringify!($name), | ||
) | ||
} | ||
|
||
$build | ||
|
||
fn partial_eq(&self, action: &dyn gpui::Action) -> bool { | ||
action | ||
.as_any() | ||
.downcast_ref::<Self>() | ||
.map_or(false, |a| self == a) | ||
} | ||
|
||
fn boxed_clone(&self) -> std::boxed::Box<dyn gpui::Action> { | ||
::std::boxed::Box::new(self.clone()) | ||
} | ||
|
||
fn as_any(&self) -> &dyn ::std::any::Any { | ||
self | ||
} | ||
} | ||
}; | ||
} | ||
|
||
mod no_action { | ||
use crate as gpui; | ||
|
||
actions!(zed, [NoAction]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
use anyhow::anyhow; | ||
|
||
use gpui::{AssetSource, Result, SharedString}; | ||
use rust_embed::RustEmbed; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,195 @@ | ||
use gpui::{div, prelude::*, Render, View, WindowContext}; | ||
use gpui::{ | ||
div, prelude::*, AnyView, ClickEvent, EventEmitter, FocusHandle, Pixels, Render, View, | ||
ViewContext, WindowContext, | ||
}; | ||
|
||
use crate::ui::{button::button::Button, clickable::Clickable, text_field::text_field::TextField}; | ||
use crate::{ | ||
components::tab_bar_container::TabEvent, | ||
repositories, | ||
state::{StackableView, StackableViewState}, | ||
theme::Theme, | ||
ui::{button::button::Button, clickable::Clickable, text_field::text_field::TextField}, | ||
Deck, | ||
}; | ||
|
||
pub struct AddCardView { | ||
text_input: TextField, | ||
deck: Deck, | ||
front_input: TextField, | ||
back_input: TextField, | ||
deck_input: TextField, | ||
focus_handle: FocusHandle, | ||
focused_at: usize, | ||
} | ||
|
||
impl EventEmitter<TabEvent> for AddCardView {} | ||
|
||
const TABBALE_FIELDS: [&str; 3] = ["front", "back", "submit"]; | ||
|
||
impl AddCardView { | ||
pub fn view(cx: &mut WindowContext) -> View<Self> { | ||
cx.new_view(|cx: &mut gpui::ViewContext<'_, AddCardView>| Self { | ||
text_input: TextField::new(cx, "Add a card".to_string()), | ||
pub fn view(deck_id: u32, cx: &mut WindowContext) -> View<Self> { | ||
cx.new_view(|cx: &mut gpui::ViewContext<'_, AddCardView>| { | ||
let deck = repositories::deck::Deck::load( | ||
deck_id, | ||
&cx.global::<crate::Collection>().storage.conn, | ||
) | ||
.unwrap(); | ||
|
||
let deck_input = TextField::new(cx, "".to_string(), true); | ||
|
||
deck_input.view.update(cx, |view, _| { | ||
view.text = deck.name.clone(); | ||
}); | ||
|
||
let front_input = TextField::new(cx, "".to_string(), false); | ||
front_input.focus(cx); | ||
|
||
let focus_handle = cx.focus_handle(); | ||
|
||
Self { | ||
deck, | ||
front_input, | ||
back_input: TextField::new(cx, "".to_string(), false), | ||
deck_input, | ||
focused_at: 0, | ||
focus_handle, | ||
} | ||
}) | ||
} | ||
|
||
fn save_click(&mut self, _event: &ClickEvent, cx: &mut ViewContext<Self>) { | ||
let collection = cx.global::<crate::Collection>(); | ||
let front = &self.front_input.view.read(&cx).text; | ||
let back = &self.back_input.view.read(&cx).text; | ||
|
||
let mut card = | ||
repositories::flash_card::FlashCard::new(self.deck.id.unwrap(), front, back, None); | ||
match card.save(&collection.storage.conn) { | ||
Ok(_) => { | ||
StackableViewState::update(|state, cx| state.pop(cx), cx); | ||
} | ||
Err(e) => { | ||
log::error!("Error saving card: {:?}", e); | ||
} | ||
} | ||
} | ||
|
||
fn save_key_down(&mut self, cx: &mut ViewContext<Self>) { | ||
let collection = cx.global::<crate::Collection>(); | ||
let front = &self.front_input.view.read(&cx).text; | ||
let back = &self.back_input.view.read(&cx).text; | ||
|
||
let mut card = | ||
repositories::flash_card::FlashCard::new(self.deck.id.unwrap(), front, back, None); | ||
match card.save(&collection.storage.conn) { | ||
Ok(_) => { | ||
StackableViewState::update(|state, cx| state.pop(cx), cx); | ||
cx.notify(); | ||
} | ||
Err(e) => { | ||
log::error!("Error saving card: {:?}", e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl Render for AddCardView { | ||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::prelude::IntoElement { | ||
div() | ||
.flex() | ||
.justify_center() | ||
.child(self.text_input.clone()) | ||
.child(Button::new("", "Click me").on_click(|event, cx| { | ||
log::info!("Button clicked: {:?}", event); | ||
})) | ||
let view = cx.view().clone(); | ||
let theme = cx.global::<Theme>(); | ||
|
||
let mut submit_btn = | ||
Button::new("create", "Create card", None).on_click(cx.listener(Self::save_click)); | ||
|
||
let focused_at = self.focused_at; | ||
let front_input = self.front_input.clone(); | ||
let back_input = self.back_input.clone(); | ||
|
||
if self.focused_at == 2 { | ||
submit_btn.focus(); | ||
} | ||
|
||
div().flex().size_full().justify_center().child( | ||
div().mt_20().child( | ||
div() | ||
.track_focus(&self.focus_handle) | ||
.flex() | ||
.w_full() | ||
.flex_col() | ||
.text_color(theme.text) | ||
.relative() | ||
.h_full() | ||
.on_key_down(move |event, wc| { | ||
view.update(wc, |add_view, vc| { | ||
let keystroke = &event.keystroke.key; | ||
|
||
match keystroke.as_str() { | ||
"tab" => { | ||
let next = (focused_at + 1) % TABBALE_FIELDS.len(); | ||
|
||
match TABBALE_FIELDS[next] { | ||
"front" => front_input.focus(vc), | ||
"back" => back_input.focus(vc), | ||
"submit" => vc.focus(&add_view.focus_handle), | ||
_ => {} | ||
} | ||
add_view.focused_at = next; | ||
} | ||
"enter" => { | ||
add_view.save_key_down(vc); | ||
} | ||
_ => {} | ||
} | ||
}); | ||
}) | ||
.child( | ||
div() | ||
.w(Pixels(500.0)) | ||
.child( | ||
div() | ||
.text_xl() | ||
.font_weight(gpui::FontWeight::EXTRA_BOLD) | ||
.child("Add a new card"), | ||
) | ||
.child( | ||
div() | ||
.mt_5() | ||
.child( | ||
div() | ||
.text_lg() | ||
.font_weight(gpui::FontWeight::BOLD) | ||
.child("Deck"), | ||
) | ||
.child(self.deck_input.clone()) | ||
.child( | ||
div() | ||
.mt_5() | ||
.text_lg() | ||
.font_weight(gpui::FontWeight::BOLD) | ||
.child("Front"), | ||
) | ||
.child(self.front_input.clone()) | ||
.child( | ||
div() | ||
.mt_5() | ||
.text_lg() | ||
.font_weight(gpui::FontWeight::BOLD) | ||
.child("Back"), | ||
) | ||
.child(self.back_input.clone()) | ||
.child(div().mt_5().flex().justify_end().child(submit_btn)), | ||
), | ||
), | ||
), | ||
) | ||
} | ||
} | ||
|
||
pub struct AddCardBuilder { | ||
pub deck_id: u32, | ||
} | ||
|
||
impl StackableView for AddCardBuilder { | ||
fn build(&self, cx: &mut WindowContext) -> AnyView { | ||
AddCardView::view(self.deck_id, cx).into() | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.