574 lines
25 KiB
Rust
Raw Normal View History

Feat/appflowy tauri UI (#1835) * chore: create folders * chore: setup taliwindcss (#1742) * chore: create folders * chore: setup taliwindcss --------- Co-authored-by: nathan <nathan@appflowy.io> Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com> * feat: greater to blockquote * fix: local variable 'text' isn't used * feat: #1061 Support markdown to create a blockquote * fix: #1732 the actions of an image look different than the ones of a code block * fix: command of double tilde to strikethrough * feat: callout (#1732) * feat: add callout plugin * refactor: add SelectionMenuItem.node factory makes calloutMenuItem more readable * feat: add color picker * feat: add popover to callout * feat: add emoji to callout * fix: store tint name * fix: remove leading underscores * fix: revert export of editor_entry * refactor: move color tint names to appflowy_editor * fix: #1732 only re-insert text node if it's parent is text node too while deleting * docs: doc comment for SelectionMenuItem.node * fix: disable callout plugin should be re-enabled after #1753 is done * fix: typo --------- Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io> * Feat/http server adapt (#1754) * integrate board plugin into document (#1675) * fix: cursor doesn't blink when opening selection menu * feat: add board plugin * feat: integrate board plugin into document * feat: add i10n and fix known bugs * feat: support jump to board page on document * feat: disable editor scroll only when the board plugin is selected * chore: dart fix * chore: remove unused files * fix: dart lint * Feat/database view (#1765) * chore: rename flowy-database to flowy-sqlite * refactor: rename flowy-grid to flowy-database * refactor: rename grid to database * refactor: rename GridEvent to DatabaseEvent * refactor: rename grid_id to database_id * refactor: rename dart code * fix: #1763 [Bug] Mouse unable to click a certain area * fix: potential async errors (#1772) * feat: Skeleton task (#1775) * chore: change tauri dev npm script * chore: setup prettier * chore: add protobuf type * chore: move test calls to separate component * chore: serve assets from app_flowy folder * chore: import poppins font * chore: install eslint, remove errors * placeholder components * chore: import colors from UI kit, footer panel * chore: reorganise components * chore: redux toolkit, navigation folders and files, navigation hooks * fix: on add folder others close * fix: tauri_dev task * fix: restore grid notification * chore: navigation items events (#1784) * chore: change tauri dev npm script * chore: setup prettier * chore: add protobuf type * chore: move test calls to separate component * chore: serve assets from app_flowy folder * chore: import poppins font * chore: install eslint, remove errors * placeholder components * chore: import colors from UI kit, footer panel * chore: reorganise components * chore: redux toolkit, navigation folders and files, navigation hooks * fix: on add folder others close * fix: tauri_dev task * fix: restore grid notification * chore: shared button * chore: folder/file popup, rename/duplicate/delete items * chore: new page types popup * fix: navitem pages padding * fix: page click mishandle * fix: folder click mishandle * chore: add other page types * fix: stop propagating on button click * fix: one alt * fix: renaming change bg * refactor: brake Navigation Panel into smaller components * chore: header panel folder * chore: focus and select all on rename popup * chore: add classname to popup * chore: navigation panel resize * Feat/appflowy tauri (#1831) * feat:grid view structure * feat:add store and refactor grid page * chore: import icons, resize grid items, change grid items style, add field type icons, reorganize grid toolbar * feat: auth screens(login, signup and confirm-account) ui done * chore: add tailwind class sorter and formatted all files * chore: group svgs into single folder * chore: resolve warnings in svg files * fix: use exported fieldType enum * fix: resolve FieldType referances * chore: auth pages fixes, replace links, replace buttons, svg fixes, navigate between pages, navigate to homepage on main button click --------- Co-authored-by: ascarbek <ascarbek@gmail.com> * ci: wanrings --------- Co-authored-by: Mikias Tilahun Abebe <mikiastilahun@gmail.com> Co-authored-by: Andreas Bichinger <andreas.bichinger@gmail.com> Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io> Co-authored-by: Askarbek Zadauly <ascarbek@gmail.com>
2023-02-10 16:26:14 +08:00
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(unused_imports)]
use crate::grid::block_test::util::GridRowTestBuilder;
use bytes::Bytes;
use flowy_client_sync::client_database::DatabaseBuilder;
use flowy_database::entities::*;
use flowy_database::services::cell::ToCellChangesetString;
use flowy_database::services::field::SelectOptionPB;
use flowy_database::services::field::*;
use flowy_database::services::grid_editor::{DatabaseRevisionEditor, GridRevisionSerde};
use flowy_database::services::row::{CreateRowRevisionPayload, RowRevisionBuilder};
use flowy_database::services::setting::GridSettingChangesetBuilder;
use flowy_error::FlowyResult;
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
use flowy_test::helper::ViewTest;
use flowy_test::FlowySDKTest;
use grid_model::*;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use strum::EnumCount;
use strum::IntoEnumIterator;
use tokio::time::sleep;
pub struct GridEditorTest {
pub sdk: FlowySDKTest,
pub view_id: String,
pub editor: Arc<DatabaseRevisionEditor>,
pub field_revs: Vec<Arc<FieldRevision>>,
pub block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
pub row_revs: Vec<Arc<RowRevision>>,
pub field_count: usize,
pub row_by_row_id: HashMap<String, RowPB>,
}
impl GridEditorTest {
pub async fn new_table() -> Self {
Self::new(DatabaseViewLayout::Grid).await
}
pub async fn new_board() -> Self {
Self::new(DatabaseViewLayout::Board).await
}
pub async fn new(layout: DatabaseViewLayout) -> Self {
let sdk = FlowySDKTest::default();
let _ = sdk.init_user().await;
let test = match layout {
DatabaseViewLayout::Grid => {
let build_context = make_test_grid();
let view_data: Bytes = build_context.into();
ViewTest::new_grid_view(&sdk, view_data.to_vec()).await
}
DatabaseViewLayout::Board => {
let build_context = make_test_board();
let view_data: Bytes = build_context.into();
ViewTest::new_board_view(&sdk, view_data.to_vec()).await
}
DatabaseViewLayout::Calendar => {
let build_context = make_test_calendar();
let view_data: Bytes = build_context.into();
ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await
}
};
let editor = sdk.grid_manager.open_database(&test.view.id).await.unwrap();
let field_revs = editor.get_field_revs(None).await.unwrap();
let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
assert_eq!(block_meta_revs.len(), 1);
// It seems like you should add the field in the make_test_grid() function.
// Because we assert the initialize count of the fields is equal to FieldType::COUNT.
assert_eq!(field_revs.len(), FieldType::COUNT);
let grid_id = test.view.id;
Self {
sdk,
view_id: grid_id,
editor,
field_revs,
block_meta_revs,
row_revs: row_pbs,
field_count: FieldType::COUNT,
row_by_row_id: HashMap::default(),
}
}
pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
self.editor.get_all_row_revs(&self.view_id).await.unwrap()
}
pub async fn grid_filters(&self) -> Vec<FilterPB> {
self.editor.get_all_filters().await.unwrap()
}
pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc<FieldRevision> {
self.field_revs
.iter()
.filter(|field_rev| {
let t_field_type: FieldType = field_rev.ty.into();
field_rev.id == field_id && t_field_type == field_type
})
.collect::<Vec<_>>()
.pop()
.unwrap()
}
/// returns the first `FieldRevision` in the build-in test grid.
/// Not support duplicate `FieldType` in test grid yet.
pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
self.field_revs
.iter()
.filter(|field_rev| {
let t_field_type: FieldType = field_rev.ty.into();
t_field_type == field_type
})
.collect::<Vec<_>>()
.pop()
.unwrap()
}
pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec<SelectOptionPB> {
let field_type = FieldType::MultiSelect;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
.unwrap();
type_option.options
}
pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB {
let field_type = FieldType::SingleSelect;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
.unwrap();
type_option
}
pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB {
let field_type = FieldType::Checklist;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<ChecklistTypeOptionPB>(field_type.into())
.unwrap();
type_option
}
pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB {
let field_type = FieldType::Checkbox;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<CheckboxTypeOptionPB>(field_type.into())
.unwrap();
type_option
}
pub fn block_id(&self) -> &str {
&self.block_meta_revs.last().unwrap().block_id
}
pub async fn update_cell<T: ToCellChangesetString>(&mut self, field_id: &str, row_id: String, cell_changeset: T) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| field_rev.id == field_id)
.unwrap();
self.editor
.update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset)
.await
.unwrap();
}
pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::RichText
})
.unwrap()
.clone();
self.update_cell(&field_rev.id, row_id, content.to_string()).await;
}
pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::SingleSelect
})
.unwrap()
.clone();
let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(&option_id);
self.update_cell(&field_rev.id, row_id, cell_changeset).await;
}
}
pub const GOOGLE: &str = "Google";
pub const FACEBOOK: &str = "Facebook";
pub const TWITTER: &str = "Twitter";
pub const COMPLETED: &str = "Completed";
pub const PLANNED: &str = "Planned";
pub const PAUSED: &str = "Paused";
pub const FIRST_THING: &str = "Wake up at 6:00 am";
pub const SECOND_THING: &str = "Get some coffee";
pub const THIRD_THING: &str = "Start working";
/// The build-in test data for grid. Currently, there are five rows in this grid, if you want to add
/// more rows or alter the data in this grid. Some tests may fail. So you need to fix the failed tests.
fn make_test_grid() -> BuildDatabaseContext {
let mut grid_builder = DatabaseBuilder::new();
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
let field_type: FieldType = field_type;
// The
match field_type {
FieldType::RichText => {
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.primary(true)
.build();
grid_builder.add_field(text_field);
}
FieldType::Number => {
// Number
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
grid_builder.add_field(number_field);
}
FieldType::DateTime => {
// Date
let date = DateTypeOptionBuilder::default()
.date_format(DateFormat::US)
.time_format(TimeFormat::TwentyFourHour);
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
grid_builder.add_field(date_field);
}
FieldType::SingleSelect => {
// Single Select
let single_select = SingleSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(COMPLETED))
.add_option(SelectOptionPB::new(PLANNED))
.add_option(SelectOptionPB::new(PAUSED));
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
grid_builder.add_field(single_select_field);
}
FieldType::MultiSelect => {
// MultiSelect
let multi_select = MultiSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(GOOGLE))
.add_option(SelectOptionPB::new(FACEBOOK))
.add_option(SelectOptionPB::new(TWITTER));
let multi_select_field = FieldBuilder::new(multi_select)
.name("Platform")
.visibility(true)
.build();
grid_builder.add_field(multi_select_field);
}
FieldType::Checkbox => {
// Checkbox
let checkbox = CheckboxTypeOptionBuilder::default();
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
grid_builder.add_field(checkbox_field);
}
FieldType::URL => {
// URL
let url = URLTypeOptionBuilder::default();
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
grid_builder.add_field(url_field);
}
FieldType::Checklist => {
let checklist = ChecklistTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(FIRST_THING))
.add_option(SelectOptionPB::new(SECOND_THING))
.add_option(SelectOptionPB::new(THIRD_THING));
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
grid_builder.add_field(checklist_field);
}
}
}
for i in 0..6 {
let block_id = grid_builder.block_id().to_owned();
let field_revs = grid_builder.field_revs();
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
match i {
0 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
FieldType::Checklist => row_builder.insert_checklist_cell(|options| options),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
FieldType::URL => row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io"),
_ => "".to_owned(),
};
}
}
1 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell(""),
FieldType::Number => row_builder.insert_number_cell("2"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
2 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
}
FieldType::MultiSelect => {
row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)])
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
3 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
4 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell(""),
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
5 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell("5"),
FieldType::DateTime => row_builder.insert_date_cell("1671938394"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
_ => {}
}
let row_rev = row_builder.build();
grid_builder.add_row(row_rev);
}
grid_builder.build()
}
// Kanban board unit test mock data
fn make_test_board() -> BuildDatabaseContext {
let mut grid_builder = DatabaseBuilder::new();
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
let field_type: FieldType = field_type;
// The
match field_type {
FieldType::RichText => {
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.primary(true)
.build();
grid_builder.add_field(text_field);
}
FieldType::Number => {
// Number
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
grid_builder.add_field(number_field);
}
FieldType::DateTime => {
// Date
let date = DateTypeOptionBuilder::default()
.date_format(DateFormat::US)
.time_format(TimeFormat::TwentyFourHour);
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
grid_builder.add_field(date_field);
}
FieldType::SingleSelect => {
// Single Select
let single_select = SingleSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(COMPLETED))
.add_option(SelectOptionPB::new(PLANNED))
.add_option(SelectOptionPB::new(PAUSED));
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
grid_builder.add_field(single_select_field);
}
FieldType::MultiSelect => {
// MultiSelect
let multi_select = MultiSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(GOOGLE))
.add_option(SelectOptionPB::new(FACEBOOK))
.add_option(SelectOptionPB::new(TWITTER));
let multi_select_field = FieldBuilder::new(multi_select)
.name("Platform")
.visibility(true)
.build();
grid_builder.add_field(multi_select_field);
}
FieldType::Checkbox => {
// Checkbox
let checkbox = CheckboxTypeOptionBuilder::default();
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
grid_builder.add_field(checkbox_field);
}
FieldType::URL => {
// URL
let url = URLTypeOptionBuilder::default();
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
grid_builder.add_field(url_field);
}
FieldType::Checklist => {
let checklist = ChecklistTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(FIRST_THING))
.add_option(SelectOptionPB::new(SECOND_THING))
.add_option(SelectOptionPB::new(THIRD_THING));
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
grid_builder.add_field(checklist_field);
}
}
}
// We have many assumptions base on the number of the rows, so do not change the number of the loop.
for i in 0..5 {
let block_id = grid_builder.block_id().to_owned();
let field_revs = grid_builder.field_revs();
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
match i {
0 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
}
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
_ => "".to_owned(),
};
}
}
1 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("B"),
FieldType::Number => row_builder.insert_number_cell("2"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
}
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
2 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::MultiSelect => {
row_builder.insert_multi_select_cell(|mut options| vec![options.remove(0)])
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
FieldType::URL => row_builder.insert_url_cell("https://github.com/AppFlowy-IO/AppFlowy"),
_ => "".to_owned(),
};
}
}
3 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
_ => "".to_owned(),
};
}
}
4 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell(""),
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(2))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
_ => {}
}
let row_rev = row_builder.build();
grid_builder.add_row(row_rev);
}
grid_builder.build()
}
// Calendar unit test mock data
fn make_test_calendar() -> BuildDatabaseContext {
todo!()
}