Skip to content

Commit

Permalink
chore: Release first version
Browse files Browse the repository at this point in the history
  • Loading branch information
nvh0412 committed Apr 24, 2024
1 parent ef0c379 commit e1b4737
Show file tree
Hide file tree
Showing 73 changed files with 6,749 additions and 1,130 deletions.
3,272 changes: 3,010 additions & 262 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
gpui = { git = "https://github.com/zed-industries/zed" }
gpui = { git = "https://github.com/zed-industries/zed", tag="v0.125.4" }
rusqlite = { version = "0.30.0", features = ["bundled"] }
chrono = "0.4"
chrono = "0.4.37"
time = "0.3"
uuid = { version = "0.8", features = ["v4"] }
env_logger = "0.9"
log = "0.4.20"
catppuccin = "1.4.0"
rust-embed = "8.2.0"
anyhow = "1.0.80"
fsrs = { git = "https://github.com/open-spaced-repetition/fsrs-rs" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.114"
smallvec = "1.13.2"
32 changes: 32 additions & 0 deletions README.md
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.

![Screenshot 2024-02-22 at 10 48 48 pm](https://github.com/nvh0412/anki-rs/assets/1973623/d74a90cd-d8e4-42ce-833d-ed51c373d42e)

## 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
1 change: 1 addition & 0 deletions assets/icons/move-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/icons/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 72 additions & 0 deletions src/action.rs
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]);
}
2 changes: 0 additions & 2 deletions src/assets.rs
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;

Expand Down
194 changes: 181 additions & 13 deletions src/components/add_card.rs
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()
}
}
19 changes: 0 additions & 19 deletions src/components/card_browser.rs

This file was deleted.

Loading

0 comments on commit e1b4737

Please sign in to comment.