diff --git a/conjugation-api/src/main.rs b/conjugation-api/src/main.rs index 2485518..e126a8c 100644 --- a/conjugation-api/src/main.rs +++ b/conjugation-api/src/main.rs @@ -15,6 +15,10 @@ enum Tense { Futuro, PresentePerfecto, Pluscuamperfecto, + Condicional, + FuturoPerfecto, + PreteritoAnterior, + CondicionalPerfecto, } impl Display for Tense { @@ -26,6 +30,10 @@ impl Display for Tense { Tense::Futuro => write!(f, "Futuro"), Tense::PresentePerfecto => write!(f, "Presente perfecto"), Tense::Pluscuamperfecto => write!(f, "Pluscuamperfecto"), + Tense::Condicional => write!(f, "Condicional"), + Tense::FuturoPerfecto => write!(f, "Futuro perfecto"), + Tense::PreteritoAnterior => write!(f, "Pretérito anterior"), + Tense::CondicionalPerfecto => write!(f, "Condicional perfecto"), } } } @@ -41,6 +49,43 @@ impl FromStr for Tense { "Futuro" => Ok(Self::Futuro), "Presente perfecto" => Ok(Self::PresentePerfecto), "Pluscuamperfecto" => Ok(Self::Pluscuamperfecto), + "Condicional" => Ok(Self::Condicional), + "Futuro perfecto" => Ok(Self::FuturoPerfecto), + "Pretérito anterior" => Ok(Self::PreteritoAnterior), + "Condicional perfecto" => Ok(Self::CondicionalPerfecto), + _ => Err(()), + } + } +} + +#[derive(Clone, juniper::GraphQLEnum)] +enum Mood { + Indicativo, + Subjuntivo, + ImperativoAfirmativo, + ImperativoNegativo, +} + +impl Display for Mood { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Indicativo => write!(f, "Indicativo"), + Self::Subjuntivo => write!(f, "Subjuntivo"), + Self::ImperativoAfirmativo => write!(f, "Imperativo Afirmativo"), + Self::ImperativoNegativo => write!(f, "Imperativo Negativo"), + } + } +} + +impl FromStr for Mood { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "Indicativo" => Ok(Self::Indicativo), + "Subjuntivo" => Ok(Self::Subjuntivo), + "Imperativo Afirmativo" => Ok(Self::ImperativoAfirmativo), + "Imperativo Negativo" => Ok(Self::ImperativoNegativo), _ => Err(()), } } @@ -77,18 +122,29 @@ impl Conjugation { } #[derive(Clone)] -struct ConjugatedVerb { +struct Verb { + infinitive: String, + infinitive_english: String, + gerundio: String, + gerundio_english: String, + tenses: Vec, +} + +#[derive(Clone)] +struct VerbTense { infinitive: String, verb_english: Option, tense: Tense, + mood: Mood, conjugations: Vec, } -impl From for ConjugatedVerb { - fn from(value: Verb) -> Self { - let Verb { +impl From for VerbTense { + fn from(value: RepositoryConjugations) -> Self { + let RepositoryConjugations { infinitive, tense, + mood, verb_english, form_1s, form_2s, @@ -100,10 +156,12 @@ impl From for ConjugatedVerb { let mut conjugations = vec![]; if let Some(spanish) = form_1s { - conjugations.push(Conjugation { - pronoun: Pronoun::Yo, - spanish, - }) + if !spanish.is_empty() { + conjugations.push(Conjugation { + pronoun: Pronoun::Yo, + spanish, + }) + } } if let Some(spanish) = form_2s { conjugations.push(Conjugation { @@ -118,10 +176,12 @@ impl From for ConjugatedVerb { }) } if let Some(spanish) = form_1p { - conjugations.push(Conjugation { - pronoun: Pronoun::Nosotros, - spanish, - }) + if !spanish.is_empty() { + conjugations.push(Conjugation { + pronoun: Pronoun::Nosotros, + spanish, + }) + } } if let Some(spanish) = form_2p { conjugations.push(Conjugation { @@ -140,15 +200,29 @@ impl From for ConjugatedVerb { infinitive, verb_english, tense: Tense::from_str(tense.as_str()).unwrap_or(Tense::Presente), + mood: Mood::from_str(mood.as_str()).unwrap_or(Mood::Indicativo), conjugations, } } } #[derive(Clone, FromRow, Debug)] -struct Verb { +struct RepositoryInfinitive { + infinitive: String, + infinitive_english: String, +} + +#[derive(Clone, FromRow, Debug)] +struct RepositoryGerund { + gerund: String, + gerund_english: String, +} + +#[derive(Clone, FromRow, Debug)] +struct RepositoryConjugations { infinitive: String, tense: String, + mood: String, verb_english: Option, form_1s: Option, form_2s: Option, @@ -159,8 +233,37 @@ struct Verb { } #[juniper::graphql_object] -#[graphql(description = "A Conjugated Verb")] -impl ConjugatedVerb { +#[graphql(description = "A Verb")] +impl Verb { + #[graphql(description = "Infinitive form of the verb")] + fn infinitive(&self) -> &str { + &self.infinitive + } + + #[graphql(description = "English translation of the infinitive")] + fn infinitive_english(&self) -> &str { + &self.infinitive_english + } + + #[graphql(description = "Gerundio")] + fn gerundio(&self) -> &str { + &self.gerundio + } + + #[graphql(description = "English translation of the gerundio form")] + fn gerundio_english(&self) -> &str { + &self.gerundio_english + } + + #[graphql(description = "Tenses")] + fn tenses(&self) -> &Vec { + &self.tenses + } +} + +#[juniper::graphql_object] +#[graphql(description = "A Verb Tense")] +impl VerbTense { #[graphql(description = "Infinitive form of the verb")] fn infinitive(&self) -> &str { &self.infinitive @@ -176,6 +279,34 @@ impl ConjugatedVerb { &self.tense } + #[graphql(description = "Mood the verb has been conjugated")] + fn mood(&self) -> &Mood { + &self.mood + } + + #[graphql(description = "Title of the combined tense and mood")] + fn title(&self) -> &str { + match (&self.tense, &self.mood) { + (Tense::Presente, Mood::Indicativo) => "Presente de Indicativo", + (Tense::Imperfecto, Mood::Indicativo) => "Imperfecto de Indicativo", + (Tense::Preterito, Mood::Indicativo) => "Pretérito", + (Tense::Futuro, Mood::Indicativo) => "Futuro", + (Tense::Condicional, Mood::Indicativo) => "Potencial Simple", + (Tense::Presente, Mood::Subjuntivo) => "Presente de Subjuntivo", + (Tense::Imperfecto, Mood::Subjuntivo) => "Imperfecto de Subjuntivo", + (Tense::PresentePerfecto, Mood::Indicativo) => "Perfecto de Indicativo", + (Tense::Pluscuamperfecto, Mood::Indicativo) => "Pluscuamperfecto de Indicativo", + (Tense::PreteritoAnterior, Mood::Indicativo) => "Pretérito Anterior", + (Tense::FuturoPerfecto, Mood::Indicativo) => "Futuro Perfecto", + (Tense::CondicionalPerfecto, Mood::Indicativo) => "Potencial Compuesto", + (Tense::PresentePerfecto, Mood::Subjuntivo) => "Perfecto de Subjuntivo", + (Tense::Pluscuamperfecto, Mood::Subjuntivo) => "Pluscuamperfecto de Subjuntivo", + (Tense::Presente, Mood::ImperativoAfirmativo) => "Imperativo Afirmativo", + (Tense::Presente, Mood::ImperativoNegativo) => "Imperativo Negativo", + _ => "", + } + } + #[graphql(description = "First person singular")] fn conjugations(&self) -> &Vec { &self.conjugations @@ -193,13 +324,68 @@ pub struct QueryRoot; #[juniper::graphql_object(context = State)] impl QueryRoot { #[graphql(description = "get a verb")] - async fn verb( + async fn verb(context: &State, infinitive: String) -> Option { + let mut query_builder: QueryBuilder = QueryBuilder::new( + "SELECT infinitive, infinitive_english FROM infinitive WHERE infinitive = ", + ); + query_builder.push_bind(infinitive.clone()); + + let query = query_builder.build_query_as::(); + + let RepositoryInfinitive { + infinitive: inf, + infinitive_english, + } = query + .fetch_optional(&context.pool) + .await + .unwrap_or_default()?; + + let mut query_builder: QueryBuilder = + QueryBuilder::new("SELECT gerund, gerund_english FROM gerund WHERE infinitive = "); + query_builder.push_bind(infinitive.clone()); + + let query = query_builder.build_query_as::(); + + let RepositoryGerund { + gerund, + gerund_english, + } = query + .fetch_optional(&context.pool) + .await + .unwrap_or_default()?; + + let mut query_builder: QueryBuilder = + QueryBuilder::new("SELECT infinitive, tense, mood, verb_english, form_1s, form_2s, form_3s, form_1p, form_2p, form_3p FROM verbs WHERE NOT (mood = 'Subjuntivo' AND (tense = 'Futuro' OR tense = 'Futuro perfecto')) AND infinitive = "); + + query_builder.push_bind(infinitive); + + let query = query_builder.build_query_as::(); + + let tenses: Vec = query + .fetch_all(&context.pool) + .await + .unwrap_or_default() + .into_iter() + .map(VerbTense::from) + .collect(); + + Some(Verb { + infinitive: inf, + infinitive_english, + gerundio: gerund, + gerundio_english: gerund_english, + tenses, + }) + } + + #[graphql(description = "get a conjugated verb")] + async fn verb_tense( context: &State, infinitive: Option, tenses: Option>, - ) -> Option { + ) -> Option { let mut query_builder: QueryBuilder = - QueryBuilder::new("SELECT infinitive, tense, verb_english, form_1s, form_2s, form_3s, form_1p, form_2p, form_3p FROM verbs WHERE mood = 'Indicativo'"); + QueryBuilder::new("SELECT infinitive, tense, mood, verb_english, form_1s, form_2s, form_3s, form_1p, form_2p, form_3p FROM verbs WHERE mood = 'Indicativo'"); if let Some(infinitive) = infinitive { query_builder.push(" AND infinitive = "); @@ -226,13 +412,13 @@ impl QueryRoot { query_builder.push(" ORDER BY RANDOM() LIMIT 1"); - let query = query_builder.build_query_as::(); + let query = query_builder.build_query_as::(); query .fetch_optional(&context.pool) .await .unwrap_or_default() - .map(ConjugatedVerb::from) + .map(VerbTense::from) } } diff --git a/conjugation-ui/schema/schema.graphql b/conjugation-ui/schema/schema.graphql index 6dfed62..212d0d4 100644 --- a/conjugation-ui/schema/schema.graphql +++ b/conjugation-ui/schema/schema.graphql @@ -3,7 +3,21 @@ schema { } type QueryRoot { "get a verb" - verb(infinitive: String, tenses: [Tense!]): ConjugatedVerb + verb(infinitive: String!): Verb + "get a conjugated verb" + verbTense(infinitive: String, tenses: [Tense!]): VerbTense +} +type Verb { + "Infinitive form of the verb" + infinitive: String! + "English translation of the infinitive" + infinitiveEnglish: String! + "Gerundio" + gerundio: String! + "English translation of the gerundio form" + gerundioEnglish: String! + "Tenses" + tenses: [VerbTense!]! } type Conjugation { "Pronoun used for the conjugation" @@ -11,13 +25,17 @@ type Conjugation { "Conjugated verb in spanish" spanish: String! } -type ConjugatedVerb { +type VerbTense { "Infinitive form of the verb" infinitive: String! "English form of the verb" verbEnglish: String "Tense the verb has been conjugated" tense: Tense! + "Mood the verb has been conjugated" + mood: Mood! + "Title of the combined tense and mood" + title: String! "First person singular" conjugations: [Conjugation!]! } @@ -36,5 +54,15 @@ enum Tense { FUTURO PRESENTE_PERFECTO PLUSCUAMPERFECTO + CONDICIONAL + FUTURO_PERFECTO + PRETERITO_ANTERIOR + CONDICIONAL_PERFECTO +} +enum Mood { + INDICATIVO + SUBJUNTIVO + IMPERATIVO_AFIRMATIVO + IMPERATIVO_NEGATIVO } diff --git a/conjugation-ui/src/components/Guess.spec.ts b/conjugation-ui/src/components/Guess.spec.ts index c94b735..3f8f3be 100644 --- a/conjugation-ui/src/components/Guess.spec.ts +++ b/conjugation-ui/src/components/Guess.spec.ts @@ -1,4 +1,3 @@ -import { Tense } from '../generated/graphql'; import Guess from './Guess.svelte'; import { render, screen, fireEvent } from '@testing-library/svelte'; import '@testing-library/jest-dom'; @@ -7,14 +6,15 @@ describe('Guess Component', () => { test('should render the component', () => { render(Guess, { infinitive: 'hablar', - tense: Tense.Presente, + english: '', + tense: 'Presente de Indicativo', pronoun: 'Yo', answer: 'hablo', correct: () => {}, finish: () => {} }); - const title = screen.getByText('presente'); + const title = screen.getByText('Presente de Indicativo'); expect(title).toBeInTheDocument(); const subtitle = screen.getByText('Yo (hablar)'); @@ -26,7 +26,7 @@ describe('Guess Component', () => { let done: boolean = false; render(Guess, { infinitive: 'hablar', - tense: Tense.Presente, + tense: 'Presente de Indicativo', pronoun: 'Yo', answer: 'hablo', correct: () => { @@ -62,7 +62,7 @@ describe('Guess Component', () => { let done: boolean = false; render(Guess, { infinitive: 'hablar', - tense: Tense.Presente, + tense: 'Presente de Indicativo', pronoun: 'Yo', answer: 'hablo', correct: () => { diff --git a/conjugation-ui/src/components/Guess.svelte b/conjugation-ui/src/components/Guess.svelte index 5a8a988..67f992d 100644 --- a/conjugation-ui/src/components/Guess.svelte +++ b/conjugation-ui/src/components/Guess.svelte @@ -1,7 +1,6 @@
-

{tense.toString().toLowerCase().replaceAll('_', ' ')}

+

{tense}

+

{english}

{pronoun} ({infinitive})

diff --git a/conjugation-ui/src/components/Practise.svelte b/conjugation-ui/src/components/Practise.svelte index 83eca6e..72912c8 100644 --- a/conjugation-ui/src/components/Practise.svelte +++ b/conjugation-ui/src/components/Practise.svelte @@ -1,24 +1,25 @@
- {#if index < verb.conjugations.length} - {#each verb.conjugations as conjugation, i} + {#if index < tense.conjugations.length} + {#each tense.conjugations as conjugation, i} {#if index === i} score++} finish={() => index++} diff --git a/conjugation-ui/src/components/Tense.svelte b/conjugation-ui/src/components/Tense.svelte new file mode 100644 index 0000000..3424ada --- /dev/null +++ b/conjugation-ui/src/components/Tense.svelte @@ -0,0 +1,51 @@ + + +
+

{tense.title}

+

{tense.verbEnglish}

+ + {#if table && !playing} + + {/if} + +
+ +
+ + {#if !playing} + + {:else} + + + + {/if} + diff --git a/conjugation-ui/src/components/Verb.svelte b/conjugation-ui/src/components/Verb.svelte index 1f738f9..01f4b7a 100644 --- a/conjugation-ui/src/components/Verb.svelte +++ b/conjugation-ui/src/components/Verb.svelte @@ -1,9 +1,8 @@ @@ -22,33 +18,17 @@ {#if infinitive} {/if} - {#if selected && selected !== UNSELECTED_TENSE} - - {/if}
- - {#if infinitive !== '' && selected !== UNSELECTED} + {#if infinitive !== ''}
{/if}
-
-
- - -
{/if} diff --git a/conjugation-ui/src/routes/verb/+page.server.ts b/conjugation-ui/src/routes/verb/+page.server.ts index 522d6ab..44bf54d 100644 --- a/conjugation-ui/src/routes/verb/+page.server.ts +++ b/conjugation-ui/src/routes/verb/+page.server.ts @@ -1,30 +1,35 @@ import axios from 'axios'; -import { Tense, type QueryRootVerbArgs } from '../../generated/graphql'; +import type { PageServerLoad } from './$types'; +import type { QueryRootVerbArgs } from '../../generated/graphql'; /** @type {import('./$types').PageServerLoad} */ -export async function load({ url }) { - const t = url.searchParams.get('tense'); - const tense = t ? Tense[t as keyof typeof Tense] : undefined; - +export async function load({ url }: PageServerLoad) { try { const variables: QueryRootVerbArgs = { infinitive: url.searchParams.get('infinitive')?.trim()?.toLowerCase(), - tenses: tense ? [tense] : undefined }; - const response = await axios.post(process.env.PUBLIC_API_ENDPOINT_URL || '', { + const response = await axios.post(process.env.PUBLIC_API_ENDPOINT_URL || 'http://0.0.0.0:8080/graphql', { query: ` - query SearchVerb($infinitive: String, $tenses: [Tense!]) { - verb(infinitive: $infinitive, tenses: $tenses) { - tense - infinitive - verbEnglish - conjugations { - pronoun - spanish - } +query SearchVerb($infinitive: String!) { + verb(infinitive: $infinitive) { + infinitive + infinitiveEnglish + gerundio + gerundioEnglish + tenses { + title + tense + mood + infinitive + verbEnglish + conjugations { + pronoun + spanish } } + } +} `, variables }); diff --git a/conjugation-ui/src/routes/verb/+page.svelte b/conjugation-ui/src/routes/verb/+page.svelte index 9ae5ccc..1af4da1 100644 --- a/conjugation-ui/src/routes/verb/+page.svelte +++ b/conjugation-ui/src/routes/verb/+page.svelte @@ -1,19 +1,19 @@ Practise - + {#if data.verb}
- +
{:else}

verb not found...