use derive_where::derive_where;
use toasty::stmt::OrderByExpr;
use topcoat::{context::Cx, view::*};
use super::{Column, ColumnBase, ColumnWidth, boolean::boolean_cell, sort_order};
use crate::{Lens, schema::Options, values::FormScalar};
mod view;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Editor {
Toggle,
Select,
Text,
}
impl Editor {
pub(crate) fn saves_on_change(self) -> bool {
matches!(self, Self::Toggle | Self::Select)
}
pub(crate) fn control(self) -> &'static str {
match self {
Self::Toggle => "a checkbox or switch",
Self::Select => "a choice over static options",
Self::Text => "a one-line text field",
}
}
}
#[derive_where(Clone, Debug)]
pub struct ToggleColumn<M> {
lens: Lens<M, bool>,
base: ColumnBase,
sortable: bool,
}
impl<M> ToggleColumn<M>
where
M: toasty::schema::Model,
{
pub fn new(lens: Lens<M, bool>) -> Self {
Self {
base: ColumnBase::new(lens.path(), ColumnWidth::Auto),
lens,
sortable: false,
}
}
base_builders!(sortable);
}
impl<M> Column<M> for ToggleColumn<M>
where
M: toasty::schema::Model + Send + Sync + 'static,
{
base_column_methods!();
fn text(&self, cx: &Cx, row: &M) -> String {
crate::Text::message(if *self.lens.read(row) {
"tablo-yes"
} else {
"tablo-no"
})
.localize(cx)
}
fn cell<'a>(&self, cx: &'a Cx, row: &M) -> BoxView<'a> {
boolean_cell(cx, *self.lens.read(row), self.text(cx, row))
}
fn order_by(&self, descending: bool) -> Option<OrderByExpr> {
self.sortable
.then(|| sort_order(self.lens.path(), descending))
}
}
impl<M> crate::column::ColumnInternals<M> for ToggleColumn<M>
where
M: toasty::schema::Model + Send + Sync + 'static,
{
base_internals!(binding);
fn edits(&self) -> Option<Editor> {
Some(Editor::Toggle)
}
fn editor<'a>(&self, cx: &'a Cx, row: &M) -> Option<BoxView<'a>> {
Some(view::toggle(
cx,
self.base.name(),
self.base.label().localize(cx),
*self.lens.read(row),
))
}
}
#[derive_where(Clone, Debug)]
pub struct SelectColumn<M, T> {
lens: Lens<M, T>,
base: ColumnBase,
sortable: bool,
}
impl<M, T> SelectColumn<M, T>
where
M: toasty::schema::Model,
T: Options + FormScalar + Send + Sync + 'static,
{
pub fn new(lens: Lens<M, T>) -> Self {
Self {
base: ColumnBase::new(lens.path(), ColumnWidth::Auto),
lens,
sortable: false,
}
}
base_builders!(sortable);
}
impl<M, T> Column<M> for SelectColumn<M, T>
where
M: toasty::schema::Model + Send + Sync + 'static,
T: Options + FormScalar + Send + Sync + 'static,
{
base_column_methods!();
fn text(&self, cx: &Cx, row: &M) -> String {
T::label_of(&self.lens.read(row).to_form()).localize(cx)
}
fn order_by(&self, descending: bool) -> Option<OrderByExpr> {
self.sortable
.then(|| sort_order(self.lens.path(), descending))
}
}
impl<M, T> crate::column::ColumnInternals<M> for SelectColumn<M, T>
where
M: toasty::schema::Model + Send + Sync + 'static,
T: Options + FormScalar + Send + Sync + 'static,
{
base_internals!(binding);
fn edits(&self) -> Option<Editor> {
Some(Editor::Select)
}
fn edit_options(&self) -> Vec<(String, crate::Text)> {
T::options()
}
fn editor<'a>(&self, cx: &'a Cx, row: &M) -> Option<BoxView<'a>> {
let options = T::options()
.into_iter()
.map(|(value, label)| (value, label.localize(cx)))
.collect();
Some(view::select(
cx,
self.base.name(),
self.base.label().localize(cx),
self.lens.read(row).to_form(),
options,
))
}
}
#[derive_where(Clone, Debug)]
pub struct TextInputColumn<M, T> {
lens: Lens<M, T>,
base: ColumnBase,
sortable: bool,
}
impl<M, T> TextInputColumn<M, T>
where
M: toasty::schema::Model,
T: FormScalar + Send + Sync + 'static,
{
pub fn new(lens: Lens<M, T>) -> Self {
Self {
base: ColumnBase::new(lens.path(), ColumnWidth::Grow),
lens,
sortable: false,
}
}
base_builders!(sortable);
}
impl<M, T> Column<M> for TextInputColumn<M, T>
where
M: toasty::schema::Model + Send + Sync + 'static,
T: FormScalar + Send + Sync + 'static,
{
base_column_methods!();
fn text(&self, cx: &Cx, row: &M) -> String {
self.lens.read(row).to_label().localize(cx)
}
fn order_by(&self, descending: bool) -> Option<OrderByExpr> {
self.sortable
.then(|| sort_order(self.lens.path(), descending))
}
}
impl<M, T> crate::column::ColumnInternals<M> for TextInputColumn<M, T>
where
M: toasty::schema::Model + Send + Sync + 'static,
T: FormScalar + Send + Sync + 'static,
{
base_internals!(binding);
fn edits(&self) -> Option<Editor> {
Some(Editor::Text)
}
fn editor<'a>(&self, cx: &'a Cx, row: &M) -> Option<BoxView<'a>> {
let value = T::input_value(&self.lens.read(row).to_form(), &crate::time_zone(cx));
Some(view::text_input(
cx,
view::TextInput {
name: self.base.name(),
label: self.base.label().localize(cx),
value,
input_type: T::INPUT_TYPE,
step: T::STEP,
},
))
}
}