chore: move client folders (#948)

This commit is contained in:
DarkSky
2023-02-10 12:41:01 +00:00
committed by GitHub
parent cb118149f3
commit 8a7393a961
235 changed files with 114 additions and 215 deletions
+32
View File
@@ -0,0 +1,32 @@
# Client App
AFFiNE App client powered by Tauri.
## Quick Start
Please follow the Tauri [getting started guide](https://tauri.app/v1/guides/getting-started/setup/) for environment setup.
After the environment is ready, start development build:
```sh
pnpm tauri dev
```
## Development
Currently desktop client depends on a rapidly developing rust library "Octobase", we use git-submodule to link it currently.
We will provide its binary binding soon, to replace the git-submodule, before Octobase become opensource.
### Scripts
On this folder:
- `pnpm dev:app` will start a vite server
- `pnpm build:prerequisite` will link the Octobase and prepare affine dist html and tauri preload script, also will generate ts type from rs. You should run this before start your first development time.
On project root folder:
### Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/src/style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AFFiNE</title>
</head>
<body>
<div id="react-root" />
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+70
View File
@@ -0,0 +1,70 @@
{
"name": "@affine/client-app",
"private": true,
"version": "0.0.0",
"type": "module",
"license": "MPL-2.0",
"module": "true",
"scripts": {
"dev:app": "pnpm build:preload && cross-env NODE_ENV=development tauri dev",
"dev:prerequisite": "concurrently \"cd ../packages/data-center && pnpm dev\" \"cd ../apps/web && pnpm dev\"",
"build:prerequisite": "pnpm build:submodules && pnpm build:rs-types && pnpm build:affine && pnpm build:preload",
"build:rs-types": "zx scripts/generateTsTypingsFromJsonSchema.mjs",
"build:submodules": "zx scripts/buildSubModules.mjs",
"build:affine": "zx scripts/buildAffine.mjs",
"build:preload": "esbuild src/preload/index.ts --outdir=public/preload",
"build:app": "tauri build"
},
"dependencies": {
"@blocksuite/store": "^0.3.1",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@tauri-apps/api": "^1.2.0",
"json-schema-to-typescript": "^11.0.2",
"lib0": "^0.2.58",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"react-router": "^6.5.0",
"react-router-dom": "^6.5.0",
"y-protocols": "^1.0.5",
"yjs": "^13.5.43"
},
"devDependencies": {
"@tauri-apps/cli": "^1.2.3",
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@typescript-eslint/eslint-plugin": "5.47.0",
"@typescript-eslint/parser": "5.47.0",
"concurrently": "^7.6.0",
"cross-env": "^7.0.3",
"esbuild": "^0.16.10",
"eslint": "8.30.0",
"eslint-config-prettier": "8.5.0",
"eslint-config-standard": "^17.0.0",
"eslint-config-standard-with-typescript": "24.0.0",
"eslint-import-resolver-alias": "1.1.2",
"eslint-import-resolver-typescript": "3.5.2",
"eslint-plugin-autofix": "1.1.0",
"eslint-plugin-html": "7.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.6.0",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-security": "1.5.0",
"eslint-plugin-security-node": "1.1.1",
"eslint-plugin-typescript-sort-keys": "2.1.0",
"eslint-plugin-unicorn": "45.0.2",
"eslint-plugin-unused-imports": "2.0.0",
"prettier": "2.8.1",
"rimraf": "^3.0.2",
"typescript": "^4.9.4",
"typesync": "^0.9.2",
"vite": "^4.0.2",
"zx": "^7.1.1"
}
}
+4041
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
const repoDirectory = path.join(__dirname, '..', '..', '..');
const clientAppDirectory = path.join(__dirname, '..');
const publicDistributionDirectory = path.join(clientAppDirectory, 'public');
const affineSrcDirectory = path.join(repoDirectory, 'packages', 'app');
const affineSrcOutDirectory = path.join(affineSrcDirectory, 'out');
const publicAffineOutDirectory = path.join(
publicDistributionDirectory,
'affine-out'
);
/**
* Build affine dist html
*/
cd(repoDirectory);
await $`pnpm i -r`;
await $`pnpm build`;
cd(affineSrcDirectory);
$.env.BASE_PATH = '/affine-out';
await $`pnpm build`;
await $`pnpm export`;
await fs.remove(publicAffineOutDirectory);
await fs.move(affineSrcOutDirectory, publicAffineOutDirectory);
+18
View File
@@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
const repoDirectory = path.join(__dirname, '..');
const publicDistributionDirectory = path.join(repoDirectory, 'public');
const octoBaseBranchName = 'master';
/**
* 1. Until OctoBase become public, we link it using submodule too.
*/
cd(`${path.join(repoDirectory, 'src-OctoBase')}`);
await $`git checkout ${octoBaseBranchName}`;
await $`git submodule update --recursive && git submodule update --remote`;
await $`git pull origin ${octoBaseBranchName}`;
await $`git reset --hard origin/${octoBaseBranchName}`;
@@ -0,0 +1,53 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import fs from 'fs';
import path from 'path';
// TODO: use https://github.com/quicktype/quicktype#installation instead
import { compileFromFile } from 'json-schema-to-typescript';
import { cd } from 'zx/core';
const projectRoot = path.join(__dirname, '..', '..');
const tsTypingsFolder = path.join(
projectRoot,
'packages/data-center/src/provider/tauri-ipc/ipc/types'
);
/**
* 1. generate JSONSchema using rs crate `schemars`, this happened on rs side script `src-tauri/examples/generate-jsonschema.rs`
*/
cd('./src-tauri');
try {
fs.mkdirSync(tsTypingsFolder);
} catch {}
await $`cargo run --example generate-jsonschema`;
/**
* 2. generate TS from JSON schema, this is efficient on NodeJS side.
*/
const fileNames = fs.readdirSync(tsTypingsFolder);
const jsonSchemaFilePaths = fileNames
.filter(fileName => fileName.endsWith('.json'))
.map(fileName => path.join(tsTypingsFolder, fileName));
await Promise.all(
jsonSchemaFilePaths.map(
async fileName =>
await compileFromFile(fileName).then(tsContent =>
fs.writeFileSync(fileName.replace('.json', '.ts'), tsContent)
)
)
);
/**
* 3. fix eslint error on generated ts files
*/
cd(path.join(projectRoot, 'packages/data-center'));
await $`eslint ${tsTypingsFolder} --ext ts --fix`;
/**
* 4. // TODO: parse all #[tauri::command] and generate ts method code
*/
+4
View File
@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
+5781
View File
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
[package]
name = "AFFiNE"
version = "0.0.0"
description = "Development Tool for BlockSuite"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.57"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.2", features = [] }
[dependencies]
bytes = "1.3.0"
ipc_types = { path = "./types" }
futures = "^0.3.25"
js-sys = "0.3.60"
jwst = { path = "../src-OctoBase/libs/jwst" }
jwst-storage = { path = "../src-OctoBase/libs/jwst-storage", features = [
"sqlite",
] }
cloud-database = { path = "../src-OctoBase/libs/cloud-database", features = [
"sqlite",
] }
project-root = "0.2.2"
schemars = "0.8.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
dotenvy = "0.15.6"
tauri = { version = "1.2", features = ["api-all", "devtools"] }
tokio = { version = "1.23.0", features = ["rt", "macros"] }
lib0 = "0.12.0"
moka = { version = "0.9.6", features = ["future"] }
yrs = { path = "../src-OctoBase/libs/vendors/y-crdt/yrs" }
y-sync = { path = "../src-OctoBase/libs/vendors/y-sync" }
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = ["custom-protocol"]
# this feature is used used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = ["tauri/custom-protocol"]
[profile.release.package.wry]
debug = true
debug-assertions = true
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
@@ -0,0 +1,37 @@
use ipc_types::{
blob::IBlobParameters, document::IDocumentParameters, user::IUserParameters,
workspace::IWorkspaceParameters,
};
/**
* convert serde to jsonschema: https://imfeld.dev/writing/generating_typescript_types_from_rust
* with way to optimize
* convert jsonschema to ts: https://github.com/bcherny/json-schema-to-typescript
*/
use project_root::get_project_root;
use schemars::{schema_for, JsonSchema};
use std::{
fs::write,
path::{Path, PathBuf},
};
fn generate<T>(path: PathBuf)
where
T: ?Sized + JsonSchema, // Sized or ?Sized are both ok, click https://zhuanlan.zhihu.com/p/21820917 to learn why
{
let schema = schema_for!(T);
let output = serde_json::to_string_pretty(&schema).unwrap();
write(path, output).expect("can not write json-schema file")
}
fn main() {
let project_root = &get_project_root().unwrap();
let mono_repo_root = Path::join(project_root, "../..");
let data_center_ipc_type_folder = Path::join(
&mono_repo_root,
"packages/data-center/src/provider/tauri-ipc/ipc/types",
);
generate::<IDocumentParameters>(Path::join(&data_center_ipc_type_folder, "document.json"));
generate::<IWorkspaceParameters>(Path::join(&data_center_ipc_type_folder, "workspace.json"));
generate::<IBlobParameters>(Path::join(&data_center_ipc_type_folder, "blob.json"));
generate::<IUserParameters>(Path::join(&data_center_ipc_type_folder, "user.json"));
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

+1
View File
@@ -0,0 +1 @@
tab_spaces = 2
+25
View File
@@ -0,0 +1,25 @@
pub mod blob;
pub mod workspace;
pub mod document;
pub mod user;
use blob::*;
use workspace::*;
use document::*;
use user::*;
pub fn invoke_handler() -> impl Fn(tauri::Invoke) + Send + Sync + 'static {
tauri::generate_handler![
update_y_document,
create_workspace,
update_workspace,
get_workspaces,
get_workspace,
create_user,
get_user,
create_doc,
get_doc,
put_blob,
get_blob
]
}
@@ -0,0 +1,68 @@
use bytes::Bytes;
use futures::{
stream::{self},
StreamExt,
};
use ipc_types::blob::{GetBlob, PutBlob};
use jwst::BlobStorage;
use crate::state::AppState;
#[tauri::command]
pub async fn put_blob<'s>(
state: tauri::State<'s, AppState>,
parameters: PutBlob,
) -> Result<String, String> {
let blob_storage = &state.0.lock().await.blob_storage;
if let Ok(path) = blob_storage
.put_blob(
// TODO: ask octobase to accept blob directly or wrap/await tauri command to create a real stream, so we don't need to construct stream manually
parameters.workspace_id,
stream::iter::<Vec<Bytes>>(vec![Bytes::from(parameters.blob)]),
)
.await
{
Ok(path)
} else {
Err("Failed to create".to_string())
}
}
#[tauri::command]
pub async fn get_blob<'s>(
state: tauri::State<'s, AppState>,
parameters: GetBlob,
) -> Result<Vec<u8>, String> {
let GetBlob { workspace_id, id } = parameters;
// TODO: check user permission? Or just assume there will only be one user
let blob_storage = &state.0.lock().await.blob_storage;
if let Ok(mut file_stream) = blob_storage.get_blob(workspace_id.clone(), id.clone()).await {
// Read all of the chunks into a vector.
let mut stream_contents = Vec::new();
let mut error_message = "".to_string();
while let Some(chunk) = file_stream.next().await {
match chunk {
Ok(chunk_bytes) => stream_contents.extend_from_slice(&chunk_bytes),
Err(err) => {
error_message = format!(
"Failed to read blob file {}/{} from stream, error: {}",
workspace_id.clone().unwrap_or_default().to_string(),
id,
err
);
}
}
}
if error_message.len() > 0 {
return Err(error_message);
}
Ok(stream_contents)
} else {
Err(format!(
"Failed to read blob file {}/{} ",
workspace_id.unwrap_or_default().to_string(),
id
))
}
}
@@ -0,0 +1,81 @@
use ipc_types::document::{
CreateDocumentParameter, GetDocumentParameter, GetDocumentResponse, YDocumentUpdate,
};
use jwst::DocStorage;
use jwst::Workspace as OctoBaseWorkspace;
use lib0::any::Any;
use crate::state::AppState;
#[tauri::command]
/// get yDoc created by create_workspace, using same id
pub async fn create_doc<'s>(
state: tauri::State<'s, AppState>,
parameters: CreateDocumentParameter,
) -> Result<(), String> {
let workspace_doc = OctoBaseWorkspace::new(parameters.workspace_id.clone());
workspace_doc.with_trx(|mut workspace_doc_transaction| {
workspace_doc_transaction.set_metadata(
"name",
Any::String(parameters.workspace_name.clone().into_boxed_str()),
);
});
if let Err(error_message) = &state
.0
.lock()
.await
.doc_db
.write_doc(parameters.workspace_id.clone(), workspace_doc.doc())
.await
{
Err(format!(
"Failed to write_doc during create_workspace with error {}",
error_message.to_string()
))
} else {
Ok(())
}
}
#[tauri::command]
/// get yDoc created by create_workspace, using same id
pub async fn get_doc<'s>(
state: tauri::State<'s, AppState>,
parameters: GetDocumentParameter,
) -> Result<GetDocumentResponse, String> {
// TODO: check user permission
let state = &state.0.lock().await;
let doc_db = &state.doc_db;
if let Ok(all_updates_of_workspace) = doc_db.all(&parameters.id).await {
let all_updates = all_updates_of_workspace
.iter()
.map(|model| model.blob.clone())
.collect::<Vec<Vec<u8>>>();
Ok(GetDocumentResponse {
updates: all_updates,
})
} else {
Err(format!(
"Failed to get yDoc from workspace {}",
parameters.id
))
}
}
#[tauri::command]
pub async fn update_y_document<'s>(
state: tauri::State<'s, AppState>,
parameters: YDocumentUpdate,
) -> Result<bool, String> {
let state = &state.0.lock().await;
let doc_db = &state.doc_db;
doc_db
.replace_with(&parameters.id.clone(), parameters.update)
.await
.ok();
Ok(true)
}
@@ -0,0 +1,53 @@
use cloud_database::{CreateUser, User};
use ipc_types::{document::CreateDocumentParameter, user::GetUserParameters};
use crate::state::AppState;
use super::document::create_doc;
#[tauri::command]
/// create new user and a private workspace
pub async fn create_user<'s>(
state: tauri::State<'s, AppState>,
parameters: CreateUser,
) -> Result<User, String> {
let new_user_result = &state
.0
.lock()
.await
.metadata_db
.create_user(parameters.clone())
.await;
match new_user_result {
Ok(new_user_option) => match new_user_option {
Some((new_user, new_workspace)) => {
// a new private workspace is created, we have to create a yDoc for it
create_doc(
state,
CreateDocumentParameter {
workspace_id: new_workspace.id.clone(),
workspace_name: parameters.name.clone(),
},
)
.await
.ok();
Ok(new_user.clone())
}
None => Err("User creation failed".to_string()),
},
Err(error_message) => Err(error_message.to_string()),
}
}
#[tauri::command]
/// get the only one user in local sqlite
pub async fn get_user<'s>(
state: tauri::State<'s, AppState>,
parameters: GetUserParameters,
) -> Result<User, String> {
let db = &state.0.lock().await.metadata_db;
match db.get_user_by_email(&parameters.email).await.ok().unwrap() {
Some(user) => Ok(user),
None => Err("User not found".to_string()),
}
}
@@ -0,0 +1,102 @@
use ipc_types::{
document::CreateDocumentParameter,
workspace::{
CreateWorkspace, CreateWorkspaceResult, GetWorkspace, GetWorkspaceResult, GetWorkspaces,
GetWorkspacesResult, UpdateWorkspace,
},
};
use crate::state::AppState;
use super::document::create_doc;
#[tauri::command]
/// create yDoc for a workspace
pub async fn get_workspaces<'s>(
state: tauri::State<'s, AppState>,
parameters: GetWorkspaces,
) -> Result<GetWorkspacesResult, String> {
match &state
.0
.lock()
.await
.metadata_db
.get_user_workspaces(parameters.user_id.to_string())
.await
{
Ok(user_workspaces) => Ok(GetWorkspacesResult {
workspaces: user_workspaces.clone(),
}),
Err(error_message) => Err(error_message.to_string()),
}
}
#[tauri::command]
/// create yDoc for a workspace
pub async fn get_workspace<'s>(
state: tauri::State<'s, AppState>,
parameters: GetWorkspace,
) -> Result<GetWorkspaceResult, String> {
match &state
.0
.lock()
.await
.metadata_db
.get_workspace_by_id(parameters.id)
.await
{
Ok(user_workspace_option) => match user_workspace_option {
Some(user_workspace) => Ok(GetWorkspaceResult {
workspace: user_workspace.clone(),
}),
None => Err("Get workspace has no result".to_string()),
},
Err(error_message) => Err(error_message.to_string()),
}
}
#[tauri::command]
/// create yDoc for a workspace
pub async fn create_workspace<'s>(
state: tauri::State<'s, AppState>,
parameters: CreateWorkspace,
) -> Result<CreateWorkspaceResult, String> {
let new_workspace_result = &state
.0
.lock()
.await
.metadata_db
.create_normal_workspace(parameters.user_id.to_string())
.await;
match new_workspace_result {
Ok(new_workspace) => {
create_doc(
state,
CreateDocumentParameter {
workspace_id: new_workspace.id.clone(),
workspace_name: parameters.name.clone(),
},
)
.await
.ok();
Ok(CreateWorkspaceResult {
id: new_workspace.id.clone(),
name: parameters.name,
})
}
Err(error_message) => Err(format!(
"Failed to create_workspace with error {}",
error_message.to_string()
)),
}
}
#[tauri::command]
pub async fn update_workspace<'s>(
state: tauri::State<'s, AppState>,
parameters: UpdateWorkspace,
) -> Result<bool, String> {
// TODO: check user permission
// No thing to update now. The avatar is update in YDoc using websocket or yrs.update
Ok(true)
}
+46
View File
@@ -0,0 +1,46 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod commands;
mod state;
use dotenvy::dotenv;
use state::AppState;
use std::env;
use tauri::TitleBarStyle;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
tauri::async_runtime::set(tokio::runtime::Handle::current());
dotenv().ok();
let preload = include_str!("../../public/preload/index.js");
let is_dev = env::var("NODE_ENV").unwrap_or_default() == "development";
let initial_path = if is_dev {
"index.html"
} else {
"affine-out/index.html"
};
tauri::Builder::default()
.manage(AppState(Mutex::new(
state::AppStateRaw::new().await.unwrap(),
)))
// manually create window here, instead of in the tauri.conf.json, to add `initialization_script` here
.setup(move |app| {
let _window =
tauri::WindowBuilder::new(app, "label", tauri::WindowUrl::App(initial_path.into()))
.title("AFFiNE")
.inner_size(1000.0, 800.0)
.title_bar_style(TitleBarStyle::Overlay)
.hidden_title(true)
.initialization_script(&preload)
.build()?;
#[cfg(debug_assertions)]
_window.open_devtools();
Ok(())
})
.invoke_handler(commands::invoke_handler())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
+48
View File
@@ -0,0 +1,48 @@
use cloud_database::SqliteDBContext;
use jwst::Workspace;
use jwst_storage::{BlobAutoStorage, DocAutoStorage};
use std::{fs, path::Path};
use tauri::api::path::document_dir;
use tokio::sync::Mutex;
pub struct AppStateRaw {
pub doc_db: DocAutoStorage,
pub blob_storage: BlobAutoStorage,
pub metadata_db: SqliteDBContext,
}
impl AppStateRaw {
pub async fn new() -> Option<AppStateRaw> {
let affine_document_path = Path::new(&document_dir()?.into_os_string()).join("affine");
let metadata_db_env = format!(
"sqlite://{}?mode=rwc",
affine_document_path
.join("metadata")
.with_extension("db")
.display()
);
let blob_db_env = format!(
"sqlite://{}?mode=rwc",
affine_document_path
.join("blob")
.with_extension("db")
.display()
);
let doc_db_env = format!(
"sqlite://{}?mode=rwc",
affine_document_path
.join("doc")
.with_extension("db")
.display()
);
fs::create_dir_all(affine_document_path.clone()).unwrap();
Some(Self {
doc_db: DocAutoStorage::init_pool(&doc_db_env).await.unwrap(),
blob_storage: BlobAutoStorage::init_pool(&blob_db_env).await.unwrap(),
metadata_db: SqliteDBContext::new(metadata_db_env).await,
})
}
}
pub struct AppState(pub Mutex<AppStateRaw>); // need pub, otherwise will be "field `0` of struct `types::state::AppState` is private"
+58
View File
@@ -0,0 +1,58 @@
{
"build": {
"beforeDevCommand": "pnpm dev:prerequisite",
"beforeBuildCommand": "pnpm build:preload && pnpm build:affine",
"devPath": "http://localhost:8080",
"distDir": "../public",
"withGlobalTauri": false
},
"package": {
"productName": "AFFiNE",
"version": "0.0.2"
},
"tauri": {
"allowlist": {
"all": true,
"fs": {
"all": true,
"scope": ["$RESOURCE", "$RESOURCE/*", "$APP/*"]
}
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "com.affine.client",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"updater": {
"active": false
}
}
}
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "ipc_types"
version = "0.1.0"
[dependencies]
jwst-storage = { path = "../../src-OctoBase/libs/jwst-storage", features = [
"sqlite",
] }
cloud-database = { path = "../../src-OctoBase/libs/cloud-database", features = [
"sqlite",
] }
project-root = "0.2.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
schemars = "0.8.3"
+20
View File
@@ -0,0 +1,20 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct PutBlob {
pub workspace_id: Option<String>,
pub blob: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct GetBlob {
pub workspace_id: Option<String>,
pub id: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum IBlobParameters {
Put(PutBlob),
Get(GetBlob),
}
@@ -0,0 +1,30 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct YDocumentUpdate {
pub update: Vec<u8>,
pub id: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct GetDocumentParameter {
pub id: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CreateDocumentParameter {
pub workspace_id: String,
pub workspace_name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct GetDocumentResponse {
pub updates: Vec<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum IDocumentParameters {
YDocumentUpdate(YDocumentUpdate),
CreateDocumentParameter(CreateDocumentParameter),
GetDocumentParameter(GetDocumentParameter),
GetDocumentResponse(GetDocumentResponse),
}
+10
View File
@@ -0,0 +1,10 @@
#[allow(unused_imports)]
extern crate serde;
extern crate schemars;
extern crate jwst_storage;
extern crate cloud_database;
pub mod blob;
pub mod document;
pub mod workspace;
pub mod user;
+15
View File
@@ -0,0 +1,15 @@
use cloud_database::{CreateUser, User};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GetUserParameters {
pub email: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub enum IUserParameters {
CreateUser(CreateUser),
User(User),
GetUserParameters(GetUserParameters),
}
@@ -0,0 +1,55 @@
use cloud_database::{WorkspaceWithPermission, WorkspaceDetail};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CreateWorkspace {
pub user_id: String,
/**
* only set name, avatar is update in datacenter to yDoc directly
*/
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GetWorkspaces {
pub user_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GetWorkspace {
pub id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CreateWorkspaceResult {
pub id: String,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GetWorkspacesResult {
pub workspaces: Vec<WorkspaceWithPermission>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GetWorkspaceResult {
pub workspace: WorkspaceDetail,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct UpdateWorkspace {
pub id: i64,
pub public: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub enum IWorkspaceParameters {
CreateWorkspace(CreateWorkspace),
GetWorkspace(GetWorkspace),
GetWorkspaces(GetWorkspaces),
GetWorkspaceResult(GetWorkspaceResult),
GetWorkspacesResult(GetWorkspacesResult),
UpdateWorkspace(UpdateWorkspace),
CreateWorkspaceResult(CreateWorkspaceResult),
}
+5
View File
@@ -0,0 +1,5 @@
# Preload Scripts
Here are preload scripts (See [tauri&#39;s doc](https://tauri.app/v1/references/architecture/inter-process-communication/isolation)). This is simillar to Electron's [preload script](https://www.electronjs.org/docs/latest/tutorial/sandbox#preload-scripts).
We pass env variables to AFFiNE side from here.
+18
View File
@@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// tauri preload script can't have `export {}`
// @ts-ignore 'index.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.ts(1208)
window.__TAURI_ISOLATION_HOOK__ = payload => {
console.log('Tauri isolation hook', payload);
return payload;
};
/**
* Give AFFiNE app code some env to know it is inside a tauri app.
*/
function setEnvironmentVariables() {
window.CLIENT_APP = true;
}
setEnvironmentVariables();
+8
View File
@@ -0,0 +1,8 @@
declare global {
interface Window {
CLIENT_APP?: boolean;
__editoVersion?: string;
}
}
export {};
+28
View File
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"moduleResolution": "Node" /* can't use NodeNext, otherwise can't find styled-components and @emotion/styled 's type, because ts won't follow `type` field in @emotion/styled 's packagejson, will follow `main` instead */,
"strict": true,
"strictNullChecks": true /* Enable strict null checks. */,
"strictFunctionTypes": true /* Enable strict checking of function types. */,
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
"sourceMap": true,
"resolveJsonModule": true,
"jsx": "react-jsx" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
"allowJs": false /* Allow javascript files to be compiled. */,
"esModuleInterop": true,
"types": ["vite/client"],
"typeRoots": ["types"],
"noEmit": true,
"experimentalDecorators": true,
"isolatedModules": true,
"skipLibCheck": true,
"noImplicitReturns": true
},
"include": ["./src"],
"exclude": ["node_modules"]
}
+27
View File
@@ -0,0 +1,27 @@
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig({
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors
clearScreen: false,
// tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true,
},
// to make use of `TAURI_DEBUG` and other env variables
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
envPrefix: ['VITE_', 'TAURI_'],
build: {
// Tauri supports es2021
target: ['es2021', 'chrome100', 'safari13'],
// don't minify for debug builds
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
// produce sourcemaps for debug builds
sourcemap: !!process.env.TAURI_DEBUG,
},
esbuild: {
jsxInject: `import React from 'react';`,
},
});