Skip to content

Commit 68e6e42

Browse files
committed
Prototype web server
1 parent 6df5fcf commit 68e6e42

File tree

4 files changed

+339
-0
lines changed

4 files changed

+339
-0
lines changed

src/bin/server.rs

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#[macro_use] extern crate rocket;
2+
3+
use rocket::fs::{FileServer, relative};
4+
use rocket::State;
5+
use rocket_dyn_templates::{Template};
6+
use serde::Serialize;
7+
8+
use fennec::prelude::*;
9+
10+
#[derive(Clone, Serialize)]
11+
struct AppContext<'a> {
12+
title: &'a str,
13+
appname: &'a str,
14+
}
15+
16+
#[derive(Clone, Serialize)]
17+
struct DefinitionsContext<'a> {
18+
app: AppContext<'a>,
19+
entries: Vec<DictionaryEntry>,
20+
}
21+
22+
#[derive(Clone, Serialize)]
23+
struct SnippetsContext<'a> {
24+
app: AppContext<'a>,
25+
snippets: Vec<SnippetRow>,
26+
}
27+
28+
#[derive(Clone, Serialize)]
29+
struct RootContext<'a> {
30+
app: AppContext<'a>,
31+
dictionary: Dictionary,
32+
notebook: Notebook,
33+
}
34+
35+
#[derive(Clone, Serialize)]
36+
struct SnippetRow {
37+
source: String,
38+
description: String,
39+
transcribed: bool,
40+
notes: Vec<Note>,
41+
words: Vec<WordRow>,
42+
}
43+
44+
#[derive(Clone, Serialize)]
45+
struct WordRow {
46+
word_type: String,
47+
is_tunic: bool,
48+
is_english: bool,
49+
has_definition: bool,
50+
text: String,
51+
glyphs: Vec<Glyph>,
52+
has_border: bool,
53+
colored: bool,
54+
}
55+
56+
#[derive(Clone, Serialize)]
57+
struct DictionaryEntry {
58+
glyphs: Vec<Glyph>,
59+
definition: String,
60+
notes: Vec<String>,
61+
}
62+
63+
#[get("/")]
64+
fn index(state: &State<RootContext>) -> Template {
65+
Template::render("index", state.app.clone())
66+
}
67+
68+
#[get("/snippets")]
69+
fn snippets(state: &State<RootContext>) -> Template {
70+
let snippets: Vec<SnippetRow> = state
71+
.notebook
72+
.snippets
73+
.iter()
74+
.map(|snip| {
75+
let source = match &snip.source {
76+
Some(Source::ManualPageNumber(page_number)) => format!("/media/manual_pages/page{:0>2}.jpg", page_number),
77+
Some(Source::ScreenshotFilename(file)) => format!("/media/screenshots/{file}"),
78+
Some(Source::Other(_)) => "/media/404".to_owned(),
79+
None => "/media/404".to_owned(),
80+
};
81+
82+
let description = snip.description.clone();
83+
84+
let transcribed = snip.transcribed;
85+
86+
let notes = snip.notes.clone();
87+
88+
let words: Vec<WordRow> = snip
89+
.words
90+
.iter()
91+
.map(|word| {
92+
match &word.word_type {
93+
WordType::English(english_word) => WordRow {
94+
word_type: "English".to_owned(),
95+
is_tunic: false,
96+
is_english: true,
97+
has_definition: true,
98+
text: english_word.text(),
99+
glyphs: vec![],
100+
has_border: false,
101+
colored: false,
102+
},
103+
WordType::Tunic(tunic_word) => {
104+
let (has_definition, definition) = state
105+
.dictionary
106+
.get(&tunic_word.into())
107+
.map(|entry| {
108+
let definition = match entry.definition() {
109+
Definition::Undefined => "[Undefined]".to_owned(),
110+
Definition::Tentative(text) => text.clone(),
111+
Definition::Confirmed(text) => text.clone(),
112+
};
113+
114+
(true, definition)
115+
})
116+
.unwrap_or((false, "".to_owned()));
117+
118+
WordRow {
119+
word_type: "Tunic".to_owned(),
120+
is_tunic: true,
121+
is_english: false,
122+
has_definition,
123+
text: definition,
124+
glyphs: tunic_word.glyphs(),
125+
has_border: tunic_word.has_border(),
126+
colored: tunic_word.colored(),
127+
}
128+
}
129+
}
130+
})
131+
.collect();
132+
133+
SnippetRow {
134+
source,
135+
description,
136+
transcribed,
137+
notes,
138+
words,
139+
}
140+
})
141+
.collect();
142+
143+
let context = SnippetsContext {
144+
app: state.app.clone(),
145+
snippets,
146+
};
147+
148+
Template::render("snippets", context)
149+
}
150+
151+
#[get("/definitions")]
152+
fn definitions(state: &State<RootContext>) -> Template {
153+
let context = DefinitionsContext {
154+
app: state.app.clone(),
155+
entries: to_dictionary_entries(&state.dictionary),
156+
};
157+
158+
Template::render("definitions", context)
159+
}
160+
161+
fn to_dictionary_entries(dictionary: &Dictionary) -> Vec<DictionaryEntry> {
162+
dictionary
163+
.entries()
164+
.iter()
165+
.map(|(word, entry)| {
166+
let glyphs = word.glyphs();
167+
let definition = match entry.definition() {
168+
Definition::Undefined => "[Undefined]".to_owned(),
169+
Definition::Tentative(text) => text.clone(),
170+
Definition::Confirmed(text) => text.clone(),
171+
};
172+
let notes: Vec<String> = entry
173+
.notes()
174+
.iter()
175+
.map(|n| n.into())
176+
.collect();
177+
178+
DictionaryEntry {
179+
glyphs,
180+
definition,
181+
notes,
182+
}
183+
})
184+
.collect()
185+
}
186+
187+
#[launch]
188+
fn rocket() -> _ {
189+
let (notebook, _yaml) = notebook_from_yaml_file(DEFAULT_NOTEBOOK_FILE)
190+
.unwrap_or_else(|error| {
191+
println!("Unable to load notebook file: {}", DEFAULT_NOTEBOOK_FILE);
192+
println!("{:?}", error);
193+
panic!("Search aborted");
194+
});
195+
196+
let (dictionary, _yaml) = dictionary_from_yaml_file(DEFAULT_DICTIONARY_FILE)
197+
.unwrap_or_else(|error| {
198+
println!("Unable to load dictionary file: {}", DEFAULT_NOTEBOOK_FILE);
199+
println!("{:?}", error);
200+
panic!("Search aborted");
201+
});
202+
203+
let root_context = RootContext {
204+
app: AppContext {
205+
title: "Fennec",
206+
appname: "Fennec",
207+
},
208+
notebook,
209+
dictionary,
210+
};
211+
212+
rocket::build()
213+
.mount("/", routes![index, definitions, snippets])
214+
.mount("/media", FileServer::from(relative!("sources")))
215+
.manage(root_context)
216+
.attach(Template::fairing())
217+
}

templates/definitions.html.hbs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!doctype html>
2+
3+
<head>
4+
<meta charset="utf-8">
5+
<title>{{title}} - Definitions</title>
6+
</head>
7+
8+
<body>
9+
10+
<table>
11+
<thead>
12+
<tr>
13+
<th>Word</th>
14+
<th>Definition</th>
15+
<th>Notes</th>
16+
</tr>
17+
</thead>
18+
<tbody>
19+
{{#each entries}}
20+
<tr>
21+
<td>
22+
[{{#each this.glyphs}}{{this}} {{/each}}]
23+
</td>
24+
<td><b>{{this.definition}}<b></td>
25+
<td>
26+
<ul>
27+
{{#each this.notes}}
28+
<li>{{this}}</li>
29+
{{/each}}
30+
</ul>
31+
</td>
32+
</tr>
33+
{{/each}}
34+
</tbody>
35+
</table>
36+
37+
</body>
38+
39+
</html>
40+

templates/index.html.hbs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
3+
<head>
4+
<meta charset="utf-8">
5+
<title>{{title}}</title>
6+
</head>
7+
8+
<body>
9+
10+
<p>Welcome to {{appname}}!</p>
11+
12+
</body>
13+
14+
</html>
15+

templates/snippets.html.hbs

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<!doctype html>
2+
3+
<head>
4+
<meta charset="utf-8">
5+
<title>{{title}} - Snippets</title>
6+
</head>
7+
8+
<body>
9+
10+
<table>
11+
<thead>
12+
<tr>
13+
<th>Source</th>
14+
<th>Snippets</th>
15+
</tr>
16+
</thead>
17+
<tbody>
18+
{{#each snippets as |snippet|}}
19+
<tr>
20+
<td style="border: 1px solid grey; padding: 10px;">
21+
<div><h3>{{snippet.description}}<h3></div>
22+
<div>
23+
<img height="400px" src="{{snippet.source}}" alt="{{snippet.source}}"/>
24+
</div>
25+
<div style="width: 400px">
26+
{{#each snippet.words as |word|}}
27+
<span>
28+
{{#if word.is_english}}
29+
{{word.text}}
30+
{{/if}}
31+
{{#if word.is_tunic}}
32+
{{#if word.has_definition}}
33+
<span style="text-decoration: underline;" title="[{{#each word.glyphs as |glyph|}} {{glyph}} {{/each}}]">{{word.text}}</span>
34+
{{else}}
35+
<span style="background-color: #C9E5EA; margin-right: 5px;">
36+
[
37+
{{#each word.glyphs as |glyph|}}
38+
{{glyph}}
39+
{{/each}}
40+
]
41+
</span>
42+
{{/if}}
43+
{{/if}}
44+
</span>
45+
{{/each}}
46+
</div>
47+
</td>
48+
<td>
49+
<div><h3>Transcribed?</h3>{{snippet.transcribed}}</span></div>
50+
<div>
51+
<h3>Notes:</h3>
52+
<ul>
53+
{{#each snippet.notes as |note|}}
54+
<li>{{note}}</li>
55+
{{/each}}
56+
</ul>
57+
<div>
58+
</td>
59+
</tr>
60+
{{/each}}
61+
</tbody>
62+
</table>
63+
64+
</body>
65+
66+
</html>
67+

0 commit comments

Comments
 (0)