mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: config grid bloc
This commit is contained in:
47
frontend/rust-lib/Cargo.lock
generated
47
frontend/rust-lib/Cargo.lock
generated
@ -57,6 +57,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.2"
|
||||
@ -1042,15 +1048,21 @@ dependencies = [
|
||||
name = "flowy-grid"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flowy-derive",
|
||||
"flowy-error",
|
||||
"flowy-grid-data-model",
|
||||
"lazy_static",
|
||||
"lib-dispatch",
|
||||
"lib-infra",
|
||||
"protobuf",
|
||||
"rust_decimal",
|
||||
"rusty-money",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1061,6 +1073,8 @@ dependencies = [
|
||||
"flowy-derive",
|
||||
"lib-infra",
|
||||
"protobuf",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1702,7 +1716,7 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"arrayvec 0.5.2",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"ryu",
|
||||
@ -2752,6 +2766,27 @@ dependencies = [
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"num-traits",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal_macros"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "184abaf7b434800e1a5a8aad3ebc8cd7498df33af72d65371d797a264713a59b"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"rust_decimal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
@ -2773,6 +2808,16 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-money"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b28f881005eac7ad8d46b6f075da5f322bd7f4f83a38720fc069694ddadd683"
|
||||
dependencies = [
|
||||
"rust_decimal",
|
||||
"rust_decimal_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
|
@ -15,6 +15,12 @@ strum_macros = "0.21"
|
||||
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
protobuf = {version = "2.18.0"}
|
||||
rust_decimal = "1.8.1"
|
||||
rusty-money = {version = "0.4.0", features = ["iso"]}
|
||||
lazy_static = "1.4.0"
|
||||
chrono = "0.4.19"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
bytes = { version = "1.0" }
|
||||
|
||||
[build-dependencies]
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "proto_gen"] }
|
||||
|
@ -1,3 +1,3 @@
|
||||
|
||||
proto_crates = ["src/event_map.rs"]
|
||||
proto_crates = ["src/event_map.rs", "src/cell_service/cell_data.rs"]
|
||||
event_files = ["src/event_map.rs"]
|
469
frontend/rust-lib/flowy-grid/src/cell_service/cell_data.rs
Normal file
469
frontend/rust-lib/flowy-grid/src/cell_service/cell_data.rs
Normal file
@ -0,0 +1,469 @@
|
||||
use crate::cell_service::util::*;
|
||||
use crate::impl_any_data;
|
||||
use bytes::Bytes;
|
||||
use chrono::format::strftime::StrftimeItems;
|
||||
use chrono::NaiveDateTime;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
|
||||
use rust_decimal::Decimal;
|
||||
use rusty_money::{
|
||||
iso::{Currency, CNY, EUR, USD},
|
||||
Money,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
pub trait StringifyAnyData {
|
||||
fn stringify_any_data(&self, data: &AnyData) -> String;
|
||||
fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError>;
|
||||
}
|
||||
|
||||
pub trait DisplayCell {
|
||||
fn display_content(&self, s: &str) -> String;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ProtoBuf, Default)]
|
||||
pub struct RichTextDescription {
|
||||
#[pb(index = 1)]
|
||||
pub format: String,
|
||||
}
|
||||
impl_any_data!(RichTextDescription, FieldType::RichText);
|
||||
|
||||
impl StringifyAnyData for RichTextDescription {
|
||||
fn stringify_any_data(&self, data: &AnyData) -> String {
|
||||
data.to_string()
|
||||
}
|
||||
|
||||
fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
|
||||
Ok(AnyData::from_str(&RichTextDescription::field_type(), s))
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayCell for RichTextDescription {
|
||||
fn display_content(&self, s: &str) -> String {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
#[derive(Debug, ProtoBuf, Default)]
|
||||
pub struct CheckboxDescription {
|
||||
#[pb(index = 1)]
|
||||
pub is_selected: bool,
|
||||
}
|
||||
impl_any_data!(CheckboxDescription, FieldType::Checkbox);
|
||||
|
||||
impl StringifyAnyData for CheckboxDescription {
|
||||
fn stringify_any_data(&self, data: &AnyData) -> String {
|
||||
data.to_string()
|
||||
}
|
||||
|
||||
fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
|
||||
let s = match string_to_bool(s) {
|
||||
true => "1",
|
||||
false => "0",
|
||||
};
|
||||
Ok(AnyData::from_str(&CheckboxDescription::field_type(), s))
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayCell for CheckboxDescription {
|
||||
fn display_content(&self, s: &str) -> String {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// Date
|
||||
#[derive(Clone, Debug, ProtoBuf)]
|
||||
pub struct DateDescription {
|
||||
#[pb(index = 1)]
|
||||
pub date_format: DateFormat,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub time_format: TimeFormat,
|
||||
}
|
||||
impl_any_data!(DateDescription, FieldType::DateTime);
|
||||
|
||||
impl std::default::Default for DateDescription {
|
||||
fn default() -> Self {
|
||||
DateDescription {
|
||||
date_format: DateFormat::default(),
|
||||
time_format: TimeFormat::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DateDescription {
|
||||
fn date_time_format_str(&self) -> String {
|
||||
format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn today_from_timestamp(&self, timestamp: i64) -> String {
|
||||
let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
|
||||
self.today_from_native(native)
|
||||
}
|
||||
|
||||
fn today_from_native(&self, naive: chrono::NaiveDateTime) -> String {
|
||||
let utc: chrono::DateTime<chrono::Utc> = chrono::DateTime::from_utc(naive, chrono::Utc);
|
||||
let local: chrono::DateTime<chrono::Local> = chrono::DateTime::from(utc);
|
||||
|
||||
let fmt_str = self.date_time_format_str();
|
||||
let output = format!("{}", local.format_with_items(StrftimeItems::new(&fmt_str)));
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayCell for DateDescription {
|
||||
fn display_content(&self, s: &str) -> String {
|
||||
match s.parse::<i64>() {
|
||||
Ok(timestamp) => {
|
||||
let native = NaiveDateTime::from_timestamp(timestamp, 0);
|
||||
self.today_from_native(native)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!("DateDescription format {} fail. error: {:?}", s, e);
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StringifyAnyData for DateDescription {
|
||||
fn stringify_any_data(&self, data: &AnyData) -> String {
|
||||
match String::from_utf8(data.value.clone()) {
|
||||
Ok(s) => match s.parse::<i64>() {
|
||||
Ok(timestamp) => {
|
||||
let native = NaiveDateTime::from_timestamp(timestamp, 0);
|
||||
self.today_from_native(native)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!("DateDescription format {} fail. error: {:?}", s, e);
|
||||
String::new()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("DateDescription stringify any_data failed. {:?}", e);
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
|
||||
let timestamp = s
|
||||
.parse::<i64>()
|
||||
.map_err(|e| FlowyError::internal().context(format!("Parse {} to i64 failed: {}", s, e)))?;
|
||||
Ok(AnyData::from_str(
|
||||
&DateDescription::field_type(),
|
||||
&format!("{}", timestamp),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, ProtoBuf_Enum)]
|
||||
pub enum DateFormat {
|
||||
Local = 0,
|
||||
US = 1,
|
||||
ISO = 2,
|
||||
Friendly = 3,
|
||||
}
|
||||
impl std::default::Default for DateFormat {
|
||||
fn default() -> Self {
|
||||
DateFormat::Friendly
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<i32> for DateFormat {
|
||||
fn from(value: i32) -> Self {
|
||||
match value {
|
||||
0 => DateFormat::Local,
|
||||
1 => DateFormat::US,
|
||||
2 => DateFormat::ISO,
|
||||
3 => DateFormat::Friendly,
|
||||
_ => {
|
||||
tracing::error!("Unsupported date format, fallback to friendly");
|
||||
DateFormat::Friendly
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DateFormat {
|
||||
pub fn value(&self) -> i32 {
|
||||
*self as i32
|
||||
}
|
||||
// https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
|
||||
pub fn format_str(&self) -> &'static str {
|
||||
match self {
|
||||
DateFormat::Local => "%Y/%m/%d",
|
||||
DateFormat::US => "%Y/%m/%d",
|
||||
DateFormat::ISO => "%Y-%m-%d",
|
||||
DateFormat::Friendly => "%b %d,%Y",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, ProtoBuf_Enum)]
|
||||
pub enum TimeFormat {
|
||||
TwelveHour = 0,
|
||||
TwentyFourHour = 1,
|
||||
}
|
||||
|
||||
impl std::convert::From<i32> for TimeFormat {
|
||||
fn from(value: i32) -> Self {
|
||||
match value {
|
||||
0 => TimeFormat::TwelveHour,
|
||||
1 => TimeFormat::TwentyFourHour,
|
||||
_ => {
|
||||
tracing::error!("Unsupported time format, fallback to TwentyFourHour");
|
||||
TimeFormat::TwentyFourHour
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeFormat {
|
||||
pub fn value(&self) -> i32 {
|
||||
*self as i32
|
||||
}
|
||||
|
||||
// https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html
|
||||
pub fn format_str(&self) -> &'static str {
|
||||
match self {
|
||||
TimeFormat::TwelveHour => "%r",
|
||||
TimeFormat::TwentyFourHour => "%R",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for TimeFormat {
|
||||
fn default() -> Self {
|
||||
TimeFormat::TwentyFourHour
|
||||
}
|
||||
}
|
||||
|
||||
// Single select
|
||||
#[derive(Clone, Debug, ProtoBuf, Default)]
|
||||
pub struct SingleSelect {
|
||||
#[pb(index = 1)]
|
||||
pub options: Vec<SelectOption>,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub disable_color: bool,
|
||||
}
|
||||
impl_any_data!(SingleSelect, FieldType::SingleSelect);
|
||||
|
||||
impl StringifyAnyData for SingleSelect {
|
||||
fn stringify_any_data(&self, data: &AnyData) -> String {
|
||||
data.to_string()
|
||||
}
|
||||
|
||||
fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
|
||||
Ok(AnyData::from_str(&SingleSelect::field_type(), s))
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayCell for SingleSelect {
|
||||
fn display_content(&self, s: &str) -> String {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple select
|
||||
#[derive(Clone, Debug, ProtoBuf, Default)]
|
||||
pub struct MultiSelect {
|
||||
#[pb(index = 1)]
|
||||
pub options: Vec<SelectOption>,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub disable_color: bool,
|
||||
}
|
||||
impl_any_data!(MultiSelect, FieldType::MultiSelect);
|
||||
impl StringifyAnyData for MultiSelect {
|
||||
fn stringify_any_data(&self, data: &AnyData) -> String {
|
||||
data.to_string()
|
||||
}
|
||||
|
||||
fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
|
||||
Ok(AnyData::from_str(&MultiSelect::field_type(), s))
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayCell for MultiSelect {
|
||||
fn display_content(&self, s: &str) -> String {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ProtoBuf, Default)]
|
||||
pub struct SelectOption {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub name: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub color: String,
|
||||
}
|
||||
|
||||
impl SelectOption {
|
||||
pub fn new(name: &str) -> Self {
|
||||
SelectOption {
|
||||
id: uuid(),
|
||||
name: name.to_owned(),
|
||||
color: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Number
|
||||
#[derive(Clone, Debug, ProtoBuf)]
|
||||
pub struct NumberDescription {
|
||||
#[pb(index = 1)]
|
||||
pub money: FlowyMoney,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub scale: u32,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub symbol: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub sign_positive: bool,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub name: String,
|
||||
}
|
||||
impl_any_data!(NumberDescription, FieldType::Number);
|
||||
|
||||
impl std::default::Default for NumberDescription {
|
||||
fn default() -> Self {
|
||||
NumberDescription {
|
||||
money: FlowyMoney::default(),
|
||||
scale: 0,
|
||||
symbol: String::new(),
|
||||
sign_positive: true,
|
||||
name: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NumberDescription {
|
||||
pub fn set_money(&mut self, money: FlowyMoney) {
|
||||
self.money = money;
|
||||
self.symbol = money.symbol();
|
||||
}
|
||||
|
||||
fn money_from_str(&self, s: &str) -> Option<String> {
|
||||
match Decimal::from_str(s) {
|
||||
Ok(mut decimal) => {
|
||||
match decimal.set_scale(self.scale) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
tracing::error!("Set decimal scale failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
decimal.set_sign_positive(self.sign_positive);
|
||||
Some(self.money.with_decimal(decimal).to_string())
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Parser money from {} failed: {:?}", s, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayCell for NumberDescription {
|
||||
fn display_content(&self, s: &str) -> String {
|
||||
match self.money_from_str(&s) {
|
||||
Some(money_str) => money_str,
|
||||
None => String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StringifyAnyData for NumberDescription {
|
||||
fn stringify_any_data(&self, data: &AnyData) -> String {
|
||||
match String::from_utf8(data.value.clone()) {
|
||||
Ok(s) => match self.money_from_str(&s) {
|
||||
Some(money_str) => money_str,
|
||||
None => String::default(),
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("NumberDescription stringify any_data failed. {:?}", e);
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_any_data(&self, s: &str) -> Result<AnyData, FlowyError> {
|
||||
let strip_symbol_money = strip_money_symbol(s);
|
||||
let decimal = Decimal::from_str(&strip_symbol_money).map_err(|err| FlowyError::internal().context(err))?;
|
||||
let money_str = decimal.to_string();
|
||||
Ok(AnyData::from_str(&NumberDescription::field_type(), &money_str))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, EnumIter, ProtoBuf_Enum)]
|
||||
pub enum FlowyMoney {
|
||||
CNY = 0,
|
||||
EUR = 1,
|
||||
USD = 2,
|
||||
}
|
||||
|
||||
impl std::default::Default for FlowyMoney {
|
||||
fn default() -> Self {
|
||||
FlowyMoney::USD
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowyMoney {
|
||||
// Currency list https://docs.rs/rusty-money/0.4.0/rusty_money/iso/index.html
|
||||
pub fn from_str(s: &str) -> FlowyMoney {
|
||||
match s {
|
||||
"CNY" => FlowyMoney::CNY,
|
||||
"EUR" => FlowyMoney::EUR,
|
||||
"USD" => FlowyMoney::USD,
|
||||
_ => FlowyMoney::CNY,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_money(money: &rusty_money::Money<Currency>) -> FlowyMoney {
|
||||
FlowyMoney::from_str(&money.currency().symbol.to_string())
|
||||
}
|
||||
|
||||
pub fn currency(&self) -> &'static Currency {
|
||||
match self {
|
||||
FlowyMoney::CNY => CNY,
|
||||
FlowyMoney::EUR => EUR,
|
||||
FlowyMoney::USD => USD,
|
||||
}
|
||||
}
|
||||
|
||||
// string_to_money("¥18,443").unwrap();
|
||||
// string_to_money("$18,443").unwrap();
|
||||
// string_to_money("€18,443").unwrap();
|
||||
pub fn code(&self) -> String {
|
||||
self.currency().iso_alpha_code.to_string()
|
||||
}
|
||||
|
||||
pub fn symbol(&self) -> String {
|
||||
self.currency().symbol.to_string()
|
||||
}
|
||||
|
||||
pub fn zero(&self) -> Money<Currency> {
|
||||
let mut decimal = Decimal::new(0, 0);
|
||||
decimal.set_sign_positive(true);
|
||||
self.with_decimal(decimal)
|
||||
}
|
||||
|
||||
pub fn with_decimal(&self, decimal: Decimal) -> Money<Currency> {
|
||||
let money = rusty_money::Money::from_decimal(decimal, self.currency());
|
||||
money
|
||||
}
|
||||
}
|
5
frontend/rust-lib/flowy-grid/src/cell_service/mod.rs
Normal file
5
frontend/rust-lib/flowy-grid/src/cell_service/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod stringify;
|
||||
mod util;
|
||||
|
||||
pub mod cell_data;
|
||||
pub use stringify::*;
|
30
frontend/rust-lib/flowy-grid/src/cell_service/stringify.rs
Normal file
30
frontend/rust-lib/flowy-grid/src/cell_service/stringify.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use crate::cell_service::cell_data::*;
|
||||
use crate::cell_service::util::*;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
|
||||
|
||||
pub trait AnyDataSerde {
|
||||
fn serialize(field: &Field, s: &str) -> Result<AnyData, FlowyError> {
|
||||
match field.field_type {
|
||||
FieldType::RichText => RichTextDescription::from(field).str_to_any_data(s),
|
||||
FieldType::Number => NumberDescription::from(field).str_to_any_data(s),
|
||||
FieldType::DateTime => DateDescription::from(field).str_to_any_data(s),
|
||||
FieldType::SingleSelect => SingleSelect::from(field).str_to_any_data(s),
|
||||
FieldType::MultiSelect => MultiSelect::from(field).str_to_any_data(s),
|
||||
FieldType::Checkbox => CheckboxDescription::from(field).str_to_any_data(s),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize(data: &AnyData, field: &Field) -> Result<String, FlowyError> {
|
||||
let _ = check_type_id(data, field)?;
|
||||
let s = match field.field_type {
|
||||
FieldType::RichText => RichTextDescription::from(field).stringify_any_data(data),
|
||||
FieldType::Number => NumberDescription::from(field).stringify_any_data(data),
|
||||
FieldType::DateTime => DateDescription::from(field).stringify_any_data(data),
|
||||
FieldType::SingleSelect => SingleSelect::from(field).stringify_any_data(data),
|
||||
FieldType::MultiSelect => MultiSelect::from(field).stringify_any_data(data),
|
||||
FieldType::Checkbox => CheckboxDescription::from(field).stringify_any_data(data),
|
||||
};
|
||||
Ok(s)
|
||||
}
|
||||
}
|
129
frontend/rust-lib/flowy-grid/src/cell_service/util.rs
Normal file
129
frontend/rust-lib/flowy-grid/src/cell_service/util.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use crate::cell_service::cell_data::FlowyMoney;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
|
||||
use lazy_static::lazy_static;
|
||||
use rust_decimal::Decimal;
|
||||
use rusty_money::{iso::Currency, Money};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
lazy_static! {
|
||||
static ref CURRENCIES_BY_SYMBOL: HashMap<String, &'static Currency> = generate_currency_by_symbol();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn generate_currency_by_symbol() -> HashMap<String, &'static Currency> {
|
||||
let mut map: HashMap<String, &'static Currency> = HashMap::new();
|
||||
|
||||
for money in FlowyMoney::iter() {
|
||||
map.insert(money.symbol(), money.currency());
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn string_to_money(money_str: &str) -> Option<Money<Currency>> {
|
||||
let mut process_money_str = String::from(money_str);
|
||||
let default_currency = FlowyMoney::from_str("CNY").currency();
|
||||
|
||||
if process_money_str.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
return if process_money_str.chars().all(char::is_numeric) {
|
||||
match Money::from_str(&process_money_str, default_currency) {
|
||||
Ok(money) => Some(money),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
let symbol = process_money_str.chars().next().unwrap().to_string();
|
||||
let mut currency = default_currency;
|
||||
|
||||
for key in CURRENCIES_BY_SYMBOL.keys() {
|
||||
if symbol.eq(key) {
|
||||
currency = CURRENCIES_BY_SYMBOL.get(key).unwrap();
|
||||
crop_letters(&mut process_money_str, 1);
|
||||
}
|
||||
}
|
||||
|
||||
match Money::from_str(&process_money_str, currency) {
|
||||
Ok(money) => Some(money),
|
||||
Err(_) => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn money_from_str(s: &str) -> Option<String> {
|
||||
match Decimal::from_str(s) {
|
||||
Ok(mut decimal) => {
|
||||
match decimal.set_scale(0) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
tracing::error!("Set scale failed. {:?}", e);
|
||||
}
|
||||
}
|
||||
decimal.set_sign_positive(true);
|
||||
Some(FlowyMoney::USD.with_decimal(decimal).to_string())
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!("Format {} to money failed, {:?}", s, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strip_money_symbol(money_str: &str) -> String {
|
||||
let mut process_money_str = String::from(money_str);
|
||||
|
||||
if !process_money_str.chars().all(char::is_numeric) {
|
||||
let symbol = process_money_str.chars().next().unwrap().to_string();
|
||||
for key in CURRENCIES_BY_SYMBOL.keys() {
|
||||
if symbol.eq(key) {
|
||||
crop_letters(&mut process_money_str, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
process_money_str
|
||||
}
|
||||
|
||||
fn crop_letters(s: &mut String, pos: usize) {
|
||||
match s.char_indices().nth(pos) {
|
||||
Some((pos, _)) => {
|
||||
s.drain(..pos);
|
||||
}
|
||||
None => {
|
||||
s.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string_to_bool(bool_str: &str) -> bool {
|
||||
let lower_case_str: &str = &bool_str.to_lowercase();
|
||||
match lower_case_str {
|
||||
"1" => true,
|
||||
"true" => true,
|
||||
"yes" => true,
|
||||
"0" => false,
|
||||
"false" => false,
|
||||
"no" => false,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uuid() -> String {
|
||||
uuid::Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
pub fn check_type_id(data: &AnyData, field: &Field) -> Result<(), FlowyError> {
|
||||
let field_type = FieldType::from_type_id(&data.type_id).map_err(|e| FlowyError::internal().context(e))?;
|
||||
if field_type != field.field_type {
|
||||
tracing::error!(
|
||||
"expected field type: {:?} but receive {:?} ",
|
||||
field_type,
|
||||
field.field_type
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
use crate::controller::GridManager;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_grid_data_model::entities::{CreateGridPayload, Grid, GridId};
|
||||
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||
use flowy_grid_data_model::entities::{
|
||||
CreateGridPayload, Grid, GridId, RepeatedFieldOrder, RepeatedRow, RepeatedRowOrder,
|
||||
};
|
||||
use lib_dispatch::prelude::{AppData, Data, DataResult};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tracing::instrument(skip(data, controller), err)]
|
||||
@ -17,7 +19,37 @@ pub(crate) async fn open_grid_handler(
|
||||
data: Data<GridId>,
|
||||
controller: AppData<Arc<GridManager>>,
|
||||
) -> DataResult<Grid, FlowyError> {
|
||||
let params: GridId = data.into_inner();
|
||||
let _params: GridId = data.into_inner();
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(data, controller), err)]
|
||||
pub(crate) async fn get_rows_handler(
|
||||
data: Data<RepeatedRowOrder>,
|
||||
controller: AppData<Arc<GridManager>>,
|
||||
) -> DataResult<Grid, FlowyError> {
|
||||
let row_orders: RepeatedRowOrder = data.into_inner();
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(data, controller), err)]
|
||||
pub(crate) async fn get_fields_handler(
|
||||
data: Data<RepeatedFieldOrder>,
|
||||
controller: AppData<Arc<GridManager>>,
|
||||
) -> DataResult<Grid, FlowyError> {
|
||||
let field_orders: RepeatedFieldOrder = data.into_inner();
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(data, controller), err)]
|
||||
pub(crate) async fn create_row_handler(
|
||||
data: Data<GridId>,
|
||||
controller: AppData<Arc<GridManager>>,
|
||||
) -> DataResult<Grid, FlowyError> {
|
||||
let id: GridId = data.into_inner();
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
@ -7,10 +7,12 @@ use strum_macros::Display;
|
||||
|
||||
pub fn create(grid_manager: Arc<GridManager>) -> Module {
|
||||
let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(grid_manager);
|
||||
|
||||
module = module
|
||||
.event(GridEvent::CreateGrid, create_grid_handler)
|
||||
.event(GridEvent::OpenGrid, open_grid_handler);
|
||||
.event(GridEvent::OpenGrid, open_grid_handler)
|
||||
.event(GridEvent::GetRows, get_rows_handler)
|
||||
.event(GridEvent::GetFields, get_fields_handler)
|
||||
.event(GridEvent::CreateRow, create_row_handler);
|
||||
|
||||
module
|
||||
}
|
||||
@ -23,4 +25,13 @@ pub enum GridEvent {
|
||||
|
||||
#[event(input = "GridId", output = "Grid")]
|
||||
OpenGrid = 1,
|
||||
|
||||
#[event(input = "RepeatedRowOrder", output = "RepeatedRow")]
|
||||
GetRows = 2,
|
||||
|
||||
#[event(input = "RepeatedFieldOrder", output = "RepeatedField")]
|
||||
GetFields = 3,
|
||||
|
||||
#[event(input = "GridId")]
|
||||
CreateRow = 4,
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod controller;
|
||||
mod event_handler;
|
||||
mod event_map;
|
||||
|
||||
mod cell_service;
|
||||
mod protobuf;
|
||||
|
64
frontend/rust-lib/flowy-grid/src/macros.rs
Normal file
64
frontend/rust-lib/flowy-grid/src/macros.rs
Normal file
@ -0,0 +1,64 @@
|
||||
#[macro_export]
|
||||
macro_rules! impl_any_data {
|
||||
($target: ident, $field_type:expr) => {
|
||||
impl_field_type_data_from_field!($target);
|
||||
impl_field_type_data_from_field_type_option!($target);
|
||||
impl_type_option_from_field_data!($target, $field_type);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_field_type_data_from_field {
|
||||
($target: ident) => {
|
||||
impl std::convert::From<&Field> for $target {
|
||||
fn from(field: &Field) -> $target {
|
||||
$target::from(&field.type_options)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_field_type_data_from_field_type_option {
|
||||
($target: ident) => {
|
||||
impl std::convert::From<&AnyData> for $target {
|
||||
fn from(any_data: &AnyData) -> $target {
|
||||
match $target::try_from(Bytes::from(any_data.value.clone())) {
|
||||
Ok(obj) => obj,
|
||||
Err(err) => {
|
||||
tracing::error!("{} convert from any data failed, {:?}", stringify!($target), err);
|
||||
$target::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_type_option_from_field_data {
|
||||
($target: ident, $field_type:expr) => {
|
||||
impl $target {
|
||||
pub fn field_type() -> FieldType {
|
||||
$field_type
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<$target> for AnyData {
|
||||
fn from(field_data: $target) -> Self {
|
||||
match field_data.try_into() {
|
||||
Ok(bytes) => {
|
||||
let bytes: Bytes = bytes;
|
||||
AnyData::from_bytes(&$target::field_type(), bytes)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Field type data convert to AnyData fail, error: {:?}", e);
|
||||
// it's impossible to fail when unwrapping the default field type data
|
||||
let default_bytes: Bytes = $target::default().try_into().unwrap();
|
||||
AnyData::from_bytes(&$target::field_type(), default_bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
1655
frontend/rust-lib/flowy-grid/src/protobuf/model/cell_data.rs
Normal file
1655
frontend/rust-lib/flowy-grid/src/protobuf/model/cell_data.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,9 @@
|
||||
pub enum GridEvent {
|
||||
CreateGrid = 0,
|
||||
OpenGrid = 1,
|
||||
GetRows = 2,
|
||||
GetFields = 3,
|
||||
CreateRow = 4,
|
||||
}
|
||||
|
||||
impl ::protobuf::ProtobufEnum for GridEvent {
|
||||
@ -38,6 +41,9 @@ impl ::protobuf::ProtobufEnum for GridEvent {
|
||||
match value {
|
||||
0 => ::std::option::Option::Some(GridEvent::CreateGrid),
|
||||
1 => ::std::option::Option::Some(GridEvent::OpenGrid),
|
||||
2 => ::std::option::Option::Some(GridEvent::GetRows),
|
||||
3 => ::std::option::Option::Some(GridEvent::GetFields),
|
||||
4 => ::std::option::Option::Some(GridEvent::CreateRow),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
@ -46,6 +52,9 @@ impl ::protobuf::ProtobufEnum for GridEvent {
|
||||
static values: &'static [GridEvent] = &[
|
||||
GridEvent::CreateGrid,
|
||||
GridEvent::OpenGrid,
|
||||
GridEvent::GetRows,
|
||||
GridEvent::GetFields,
|
||||
GridEvent::CreateRow,
|
||||
];
|
||||
values
|
||||
}
|
||||
@ -74,8 +83,9 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x0fevent_map.proto*)\n\tGridEvent\x12\x0e\n\nCreateGrid\x10\0\x12\x0c\
|
||||
\n\x08OpenGrid\x10\x01b\x06proto3\
|
||||
\n\x0fevent_map.proto*T\n\tGridEvent\x12\x0e\n\nCreateGrid\x10\0\x12\x0c\
|
||||
\n\x08OpenGrid\x10\x01\x12\x0b\n\x07GetRows\x10\x02\x12\r\n\tGetFields\
|
||||
\x10\x03\x12\r\n\tCreateRow\x10\x04b\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
@ -1,5 +1,8 @@
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
// Auto-generated, do not edit
|
||||
|
||||
mod cell_data;
|
||||
pub use cell_data::*;
|
||||
|
||||
mod event_map;
|
||||
pub use event_map::*;
|
||||
|
@ -0,0 +1,47 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message RichTextDescription {
|
||||
string format = 1;
|
||||
}
|
||||
message CheckboxDescription {
|
||||
bool is_selected = 1;
|
||||
}
|
||||
message DateDescription {
|
||||
DateFormat date_format = 1;
|
||||
TimeFormat time_format = 2;
|
||||
}
|
||||
message SingleSelect {
|
||||
repeated SelectOption options = 1;
|
||||
bool disable_color = 2;
|
||||
}
|
||||
message MultiSelect {
|
||||
repeated SelectOption options = 1;
|
||||
bool disable_color = 2;
|
||||
}
|
||||
message SelectOption {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
string color = 3;
|
||||
}
|
||||
message NumberDescription {
|
||||
FlowyMoney money = 1;
|
||||
uint32 scale = 2;
|
||||
string symbol = 3;
|
||||
bool sign_positive = 4;
|
||||
string name = 5;
|
||||
}
|
||||
enum DateFormat {
|
||||
Local = 0;
|
||||
US = 1;
|
||||
ISO = 2;
|
||||
Friendly = 3;
|
||||
}
|
||||
enum TimeFormat {
|
||||
TwelveHour = 0;
|
||||
TwentyFourHour = 1;
|
||||
}
|
||||
enum FlowyMoney {
|
||||
CNY = 0;
|
||||
EUR = 1;
|
||||
USD = 2;
|
||||
}
|
@ -3,4 +3,7 @@ syntax = "proto3";
|
||||
enum GridEvent {
|
||||
CreateGrid = 0;
|
||||
OpenGrid = 1;
|
||||
GetRows = 2;
|
||||
GetFields = 3;
|
||||
CreateRow = 4;
|
||||
}
|
||||
|
Reference in New Issue
Block a user