add paginator

session-crate
Ondřej Hruška 5 years ago
parent 1f4af57b6e
commit f2a834c977
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 51
      src/main.rs
  2. 11
      src/store/mod.rs
  3. 23
      templates/index.html.tera
  4. 54
      templates/static/style.css

@ -30,16 +30,33 @@ use indexmap::map::IndexMap;
pub struct ListContext<'a> { pub struct ListContext<'a> {
pub fields: Vec<RenderedField<'a>>, pub fields: Vec<RenderedField<'a>>,
pub cards: Vec<RenderedCard<'a>>, pub cards: Vec<RenderedCard<'a>>,
pub page: usize,
pub pages: usize,
} }
#[get("/")] #[get("/?<page>&<card>")]
fn route_index(store: State<RwLock<Store>>) -> Template { fn route_index(store: State<RwLock<Store>>, page: Option<usize>, card: Option<usize>) -> Template {
let rg = store.read(); let rg = store.read();
let per_page: usize = 20;
let mut page = page.unwrap_or_default();
let n_pages = (rg.data.cards.len() as f64 / per_page as f64).ceil() as usize;
if let Some(card_id) = card {
if let Some((n, _)) = rg.data.cards.iter().enumerate().find(|(_n, (id, _card))| **id == card_id) {
page = n / per_page;
}
}
let context = ListContext { let context = ListContext {
fields: render_empty_fields(&rg), fields: render_empty_fields(&rg),
cards: rg.data.cards.iter().filter_map(|(id, card)| { pages: n_pages,
if let Value::Object(map) = card { page,
cards: rg.data.cards.iter()
.skip(page * per_page)
.take(per_page)
.filter_map(|(id, card)| {
if let Value::Object(ref map) = card {
Some(RenderedCard { Some(RenderedCard {
fields: render_card_fields(&rg, map), fields: render_card_fields(&rg, map),
id: *id, id: *id,
@ -71,7 +88,7 @@ impl<'a> FromForm<'a> for MapFromForm {
} }
} }
fn collect_card_form(store : &Store, mut form : MapFromForm) -> IndexMap::<String, Value> { fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap::<String, Value> {
let mut card = IndexMap::new(); let mut card = IndexMap::new();
for (k, field) in &store.model.fields { for (k, field) in &store.model.fields {
@ -179,7 +196,7 @@ struct AddCardContext<'a> {
#[derive(Serialize)] #[derive(Serialize)]
struct EditCardContext<'a> { struct EditCardContext<'a> {
pub fields: Vec<RenderedField<'a>>, pub fields: Vec<RenderedField<'a>>,
pub id : usize, pub id: usize,
} }
#[get("/add")] #[get("/add")]
@ -198,16 +215,16 @@ fn route_add_save(form: Form<MapFromForm>, store: State<RwLock<Store>>) -> Redir
let mut rg = store.write(); let mut rg = store.write();
let card = collect_card_form(&rg, form.into_inner()); let card = collect_card_form(&rg, form.into_inner());
rg.add_card(card); let id = rg.add_card(card);
Redirect::found(uri!(route_index)) Redirect::found(uri!(route_index: page=_, card=id))
} }
#[get("/edit/<id>")] #[get("/edit/<id>")]
fn route_edit(id : usize, store: State<RwLock<Store>>) -> Option<Template> { fn route_edit(id: usize, store: State<RwLock<Store>>) -> Option<Template> {
let rg = store.read(); let rg = store.read();
if let Some(Value::Object(map)) = rg.data.cards.get(&id) { if let Some(Value::Object(ref map)) = rg.data.cards.get(&id) {
let context = EditCardContext { let context = EditCardContext {
fields: render_card_fields(&rg, map), fields: render_card_fields(&rg, map),
id, id,
@ -220,13 +237,22 @@ fn route_edit(id : usize, store: State<RwLock<Store>>) -> Option<Template> {
} }
#[post("/edit/<id>", data = "<form>")] #[post("/edit/<id>", data = "<form>")]
fn route_edit_save(id : usize, form: Form<MapFromForm>, store: State<RwLock<Store>>) -> Option<Redirect> { fn route_edit_save(id: usize, form: Form<MapFromForm>, store: State<RwLock<Store>>) -> Option<Redirect> {
let mut rg = store.write(); let mut rg = store.write();
let card = collect_card_form(&rg, form.into_inner()); let card = collect_card_form(&rg, form.into_inner());
rg.update_card(id, card); rg.update_card(id, card);
Some(Redirect::found(uri!(route_index))) Some(Redirect::found(uri!(route_index: page=_, card=id)))
}
#[get("/delete/<id>")]
fn route_delete(id: usize, store: State<RwLock<Store>>) -> Redirect {
let mut rg = store.write();
rg.delete_card(id);
Redirect::found(uri!(route_index: page=_, card=_))
} }
fn main() { fn main() {
@ -244,5 +270,6 @@ fn main() {
route_add_save, route_add_save,
route_edit, route_edit,
route_edit_save, route_edit_save,
route_delete,
]).launch(); ]).launch();
} }

@ -75,16 +75,17 @@ impl Store {
file.write(serialized.as_bytes()).expect("Error write data file"); file.write(serialized.as_bytes()).expect("Error write data file");
} }
pub fn add_card(&mut self, values : IndexMap::<String, Value>) { pub fn add_card(&mut self, values : IndexMap::<String, Value>) -> usize {
let packed = serde_json::to_value(values).expect("Error serialize"); let packed = serde_json::to_value(values).expect("Error serialize");
if let p @ Value::Object(_) = packed { if let p @ Value::Object(_) = packed {
let id = self.data.counter; let id = self.data.counter;
self.data.counter += 1; self.data.counter += 1;
self.data.cards.insert(id, p); self.data.cards.insert(id, p);
self.persist();
id
} else { } else {
panic!("Packing did not produce a map."); panic!("Packing did not produce a map.");
} }
self.persist()
} }
pub fn update_card(&mut self, id : usize, values : IndexMap::<String, Value>) { pub fn update_card(&mut self, id : usize, values : IndexMap::<String, Value>) {
@ -102,6 +103,12 @@ impl Store {
self.persist() self.persist()
} }
pub fn delete_card(&mut self, id : usize) {
self.data.cards.remove(&id);
self.persist()
}
} }
fn load_file(path: impl AsRef<Path>) -> String { fn load_file(path: impl AsRef<Path>) -> String {

@ -22,7 +22,10 @@
<tbody> <tbody>
{%- for card in cards %} {%- for card in cards %}
<tr> <tr>
<td><a href="/edit/{{card.id}}">Edit</a></td> <td class="actions">
<a href="/edit/{{card.id}}">Edit</a>
<a href="/delete/{{card.id}}" onclick="return confirm('Confirm delete')">Delete</a>
</td>
{%- for field in card.fields %} {%- for field in card.fields %}
<td> <td>
{%- if field.kind == "bool" -%} {%- if field.kind == "bool" -%}
@ -40,4 +43,22 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<nav class="paginate">
{% if pages > 1 %}
{% if page > 0 %}
<a href="/?page={{ page - 1 }}">«</a>
{% else %}
<span class="disabled">«</span>
{% endif %}
<span class="num" onclick="location.href='/?page='+(prompt('Page')-1);">{{ page + 1 }} of {{ pages }}</span>
{% if page < pages - 1 %}
<a href="/?page={{ page + 1 }}">»</a>
{% else %}
<span class="disabled">»</span>
{% endif %}
{% endif %}
</nav>
{%- endblock %} {%- endblock %}

@ -19,6 +19,16 @@ nav.top-nav {
border-bottom: 1px solid silver; border-bottom: 1px solid silver;
} }
a {
color: gray;
text-decoration: none;
}
a:hover {
color: black;
text-decoration: underline;
}
nav.top-nav a { nav.top-nav a {
display: inline-block; display: inline-block;
padding: .75rem; padding: .75rem;
@ -112,6 +122,10 @@ textarea:focus,
margin-top: 1rem; margin-top: 1rem;
} }
.cards-table .actions {
font-size: 90%;
}
.cards-table td, .cards-table td,
.cards-table th { .cards-table th {
padding: .5rem; padding: .5rem;
@ -128,3 +142,43 @@ textarea:focus,
.cards-table tbody tr:last-child td { .cards-table tbody tr:last-child td {
border-bottom: 2px solid silver; border-bottom: 2px solid silver;
} }
.paginate {
margin: 1rem auto;
width: 300px;
text-align: center;
white-space: nowrap;
}
.paginate span,
.paginate a {
padding: .5rem 1rem;
border-radius: .5rem;
border: 1px solid silver;
text-decoration: none;
}
.paginate span.num {
border: 1px solid #ccc;
color: gray;
}
.paginate a {
cursor: pointer;
user-select: none;
}
.paginate a:hover {
background: #ccc;
text-decoration: none;
}
.paginate .disabled {
opacity: .5;
cursor: default;
}
.paginate .disabled:hover {
color: gray;
background: transparent;
}

Loading…
Cancel
Save