Merge pull request #935 from toeverything/feat/client-app
feat: tauri desktop client app
74
.github/workflows/client-app.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
name: Release App
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
paths-ignore:
|
||||||
|
- 'README.md'
|
||||||
|
- 'docs/**'
|
||||||
|
- '.vscode'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'README.md'
|
||||||
|
- '.vscode'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-ci-group
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
MacOS:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18.x
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
- name: Get npm cache directory
|
||||||
|
uses: actions/cache@v3
|
||||||
|
id: cache
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i -r
|
||||||
|
|
||||||
|
- name: Make macOS (x64)
|
||||||
|
run: pnpm build:app
|
||||||
|
working-directory: client-app
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
CI_PULL_REQUEST: ${{ github.event_name == 'pull_request' }}
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
generate_release_notes: true
|
||||||
|
files: client-app/src-tauri/target/release/bundle/dmg/*.dmg
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
4
.gitignore
vendored
@@ -58,3 +58,7 @@ module-resolve.cjs
|
|||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|
||||||
|
# generated assets
|
||||||
|
client-app/public/affine-out
|
||||||
|
client-app/public/preload
|
||||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "client-app/src-OctoBase"]
|
||||||
|
path = client-app/src-OctoBase
|
||||||
|
url = https://github.com/toeverything/OctoBase
|
||||||
15
.vscode/settings.json
vendored
@@ -8,13 +8,24 @@
|
|||||||
"datacenter",
|
"datacenter",
|
||||||
"livedemo",
|
"livedemo",
|
||||||
"pnpm",
|
"pnpm",
|
||||||
|
"jwst",
|
||||||
|
"testid",
|
||||||
|
"octobase",
|
||||||
"selfhosted",
|
"selfhosted",
|
||||||
"testid"
|
"testid",
|
||||||
|
"schemars"
|
||||||
],
|
],
|
||||||
"explorer.fileNesting.patterns": {
|
"explorer.fileNesting.patterns": {
|
||||||
"*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map",
|
"*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map",
|
||||||
"package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, cypress.*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.config.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json",
|
"package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, cypress.*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.config.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json",
|
||||||
"Cargo.toml": "Cargo.lock",
|
"Cargo.toml": "Cargo.lock",
|
||||||
"README.md": "LICENSE, CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md"
|
"README.md": "LICENSE, CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md"
|
||||||
}
|
},
|
||||||
|
"[rust]": {
|
||||||
|
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||||
|
},
|
||||||
|
"[toml]": {
|
||||||
|
"editor.defaultFormatter": "tamasfe.even-better-toml"
|
||||||
|
},
|
||||||
|
"rust-analyzer.linkedProjects": ["client-app/src-tauri/Cargo.toml"]
|
||||||
}
|
}
|
||||||
|
|||||||
32
client-app/README.md
Normal 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 client-app 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
client-app/index.html
Normal 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
client-app/package.json
Normal 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 ../packages/app && 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
client-app/pnpm-lock.yaml
generated
Normal file
28
client-app/scripts/buildAffine.mjs
Normal 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
client-app/scripts/buildSubModules.mjs
Normal 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}`;
|
||||||
53
client-app/scripts/generateTsTypingsFromJsonSchema.mjs
Normal file
@@ -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
|
||||||
|
*/
|
||||||
1
client-app/src-OctoBase
Submodule
4
client-app/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
5781
client-app/src-tauri/Cargo.lock
generated
Normal file
50
client-app/src-tauri/Cargo.toml
Normal 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
client-app/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
37
client-app/src-tauri/examples/generate-jsonschema.rs
Normal file
@@ -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"));
|
||||||
|
}
|
||||||
BIN
client-app/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
client-app/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
client-app/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 947 B |
BIN
client-app/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
client-app/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
client-app/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
client-app/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
client-app/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
client-app/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
client-app/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
client-app/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
client-app/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
client-app/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
client-app/src-tauri/icons/icon.icns
Normal file
BIN
client-app/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 85 KiB |
1
client-app/src-tauri/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tab_spaces = 2
|
||||||
25
client-app/src-tauri/src/commands.rs
Normal 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
|
||||||
|
]
|
||||||
|
}
|
||||||
68
client-app/src-tauri/src/commands/blob.rs
Normal file
@@ -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
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
81
client-app/src-tauri/src/commands/document.rs
Normal file
@@ -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(¶meters.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(¶meters.id.clone(), parameters.update)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
53
client-app/src-tauri/src/commands/user.rs
Normal file
@@ -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(¶meters.email).await.ok().unwrap() {
|
||||||
|
Some(user) => Ok(user),
|
||||||
|
None => Err("User not found".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
102
client-app/src-tauri/src/commands/workspace.rs
Normal file
@@ -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
client-app/src-tauri/src/main.rs
Normal 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
client-app/src-tauri/src/state.rs
Normal 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
client-app/src-tauri/tauri.conf.json
Normal 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
client-app/src-tauri/types/Cargo.toml
Normal 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
client-app/src-tauri/types/src/blob.rs
Normal 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),
|
||||||
|
}
|
||||||
30
client-app/src-tauri/types/src/document.rs
Normal file
@@ -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
client-app/src-tauri/types/src/lib.rs
Normal 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
client-app/src-tauri/types/src/user.rs
Normal 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),
|
||||||
|
}
|
||||||
55
client-app/src-tauri/types/src/workspace.rs
Normal file
@@ -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
client-app/src/preload/Readme.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Preload Scripts
|
||||||
|
|
||||||
|
Here are preload scripts (See [tauri'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
client-app/src/preload/index.ts
Normal 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
client-app/src/preload/type.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
CLIENT_APP?: boolean;
|
||||||
|
__editoVersion?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
28
client-app/tsconfig.json
Normal 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
client-app/vite.config.ts
Normal 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';`,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -6,10 +6,11 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
|
"dev": "cross-env NODE_ENV=development pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
|
||||||
"dev:ac": "pnpm --filter=!@affine/app build && NODE_API_SERVER=ac pnpm --filter @affine/app dev",
|
"dev:ac": "pnpm --filter=!@affine/app build && cross-env NODE_API_SERVER=ac pnpm --filter @affine/app dev",
|
||||||
"dev:local": "pnpm --filter=!@affine/app build && cross-env NODE_API_SERVER=local pnpm --filter @affine/app dev",
|
"dev:local": "pnpm --filter=!@affine/app build && cross-env NODE_API_SERVER=local pnpm --filter @affine/app dev",
|
||||||
"build": " pnpm --filter=!@affine/app build && pnpm --filter!=@affine/datacenter -r build",
|
"build": " pnpm --filter=!@affine/app build && pnpm --filter!=@affine/datacenter -r build",
|
||||||
|
"build:client": " pnpm --filter=@affine/client-app build:app",
|
||||||
"build:storybook": " pnpm -r build-storybook",
|
"build:storybook": " pnpm -r build-storybook",
|
||||||
"export": "pnpm --filter @affine/app export",
|
"export": "pnpm --filter @affine/app export",
|
||||||
"start": "pnpm --filter @affine/app start",
|
"start": "pnpm --filter @affine/app start",
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"@types/node": "^18.11.17",
|
"@types/node": "^18.11.17",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.47.0",
|
"@typescript-eslint/eslint-plugin": "^5.47.0",
|
||||||
"@typescript-eslint/parser": "^5.47.0",
|
"@typescript-eslint/parser": "^5.47.0",
|
||||||
|
"concurrently": "^7.6.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.30.0",
|
"eslint": "^8.30.0",
|
||||||
"eslint-config-next": "12.3.1",
|
"eslint-config-next": "12.3.1",
|
||||||
@@ -80,6 +82,7 @@
|
|||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
"@tauri-apps/api@1.2.0": "patches/@tauri-apps__api@1.2.0.patch",
|
||||||
"next@13.1.0": "patches/next@13.1.0.patch"
|
"next@13.1.0": "patches/next@13.1.0.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const EditorHeader = () => {
|
|||||||
>
|
>
|
||||||
{title && (
|
{title && (
|
||||||
<StyledTitle
|
<StyledTitle
|
||||||
|
data-tauri-drag-region
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
if (isTrash) return;
|
if (isTrash) return;
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,11 @@ export const Header = ({
|
|||||||
setShowWarning(false);
|
setShowWarning(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<StyledHeader hasWarning={showWarning} data-testid="editor-header-items">
|
<StyledHeader
|
||||||
|
hasWarning={showWarning}
|
||||||
|
data-testid="editor-header-items"
|
||||||
|
data-tauri-drag-region
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
<StyledHeaderRightSide>
|
<StyledHeaderRightSide>
|
||||||
{rightItems.map(itemName => {
|
{rightItems.map(itemName => {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const AppDefender = ({ children }: PropsWithChildren) => {
|
|||||||
const { synced } = useAppState();
|
const { synced } = useAppState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.asPath === '/') {
|
if (['/index.html', '/'].includes(router.asPath)) {
|
||||||
router.replace('/workspace');
|
router.replace('/workspace');
|
||||||
}
|
}
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ export const AppStateProvider = ({
|
|||||||
if (dataCenter.workspaces.length === 0) {
|
if (dataCenter.workspaces.length === 0) {
|
||||||
await createDefaultWorkspace(dataCenter);
|
await createDefaultWorkspace(dataCenter);
|
||||||
}
|
}
|
||||||
setUser((await dataCenter.getUserInfo()) || null);
|
setUser(
|
||||||
|
(await dataCenter.getUserInfo(dataCenter.providers[0]?.id)) || null
|
||||||
|
);
|
||||||
setAppState({
|
setAppState({
|
||||||
dataCenter,
|
dataCenter,
|
||||||
workspaceList: dataCenter.workspaces,
|
workspaceList: dataCenter.workspaces,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blocksuite/blocks": "0.4.0-20230210031655-264744e",
|
"@blocksuite/blocks": "0.4.0-20230210031655-264744e",
|
||||||
"@blocksuite/store": "0.4.0-20230210031655-264744e",
|
"@blocksuite/store": "0.4.0-20230210031655-264744e",
|
||||||
|
"@tauri-apps/api": "^1.2.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"firebase": "^9.15.0",
|
"firebase": "^9.15.0",
|
||||||
|
|||||||
@@ -46,7 +46,16 @@ export class DataCenter {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
// TODO: switch different provider
|
// TODO: switch different provider
|
||||||
await dc.registerProvider(new LocalProvider(getInitParams()));
|
if (
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
window.CLIENT_APP &&
|
||||||
|
typeof window.__TAURI_IPC__ === 'function'
|
||||||
|
) {
|
||||||
|
const { TauriIPCProvider } = await import('./provider/tauri-ipc');
|
||||||
|
await dc.registerProvider(new TauriIPCProvider(getInitParams()));
|
||||||
|
} else {
|
||||||
|
await dc.registerProvider(new LocalProvider(getInitParams()));
|
||||||
|
}
|
||||||
await dc.registerProvider(new AffineProvider(getInitParams()));
|
await dc.registerProvider(new AffineProvider(getInitParams()));
|
||||||
|
|
||||||
for (const provider of dc.providerMap.values()) {
|
for (const provider of dc.providerMap.values()) {
|
||||||
|
|||||||
8
packages/data-center/src/global-types.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
CLIENT_APP?: boolean;
|
||||||
|
__editoVersion?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
@@ -27,7 +27,7 @@ export enum WorkspaceType {
|
|||||||
export enum PermissionType {
|
export enum PermissionType {
|
||||||
Read = 0,
|
Read = 0,
|
||||||
Write = 1,
|
Write = 1,
|
||||||
Admin = 2,
|
Admin = 10,
|
||||||
Owner = 99,
|
Owner = 99,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { PutBlob, GetBlob } from '../ipc/types/blob';
|
||||||
|
import {
|
||||||
|
YDocumentUpdate,
|
||||||
|
GetDocumentParameter,
|
||||||
|
GetDocumentResponse,
|
||||||
|
} from '../ipc/types/document';
|
||||||
|
import { CreateUser, GetUserParameters, User } from '../ipc/types/user';
|
||||||
|
import {
|
||||||
|
CreateWorkspace,
|
||||||
|
CreateWorkspaceResult,
|
||||||
|
GetWorkspaces,
|
||||||
|
GetWorkspacesResult,
|
||||||
|
GetWorkspace,
|
||||||
|
GetWorkspaceResult,
|
||||||
|
} from '../ipc/types/workspace';
|
||||||
|
|
||||||
|
export const updateYDocument = async (parameters: YDocumentUpdate) =>
|
||||||
|
await true;
|
||||||
|
|
||||||
|
export const getYDocument = async (
|
||||||
|
parameters: GetDocumentParameter
|
||||||
|
): Promise<GetDocumentResponse> =>
|
||||||
|
await {
|
||||||
|
updates: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createWorkspace = async (
|
||||||
|
parameters: CreateWorkspace
|
||||||
|
): Promise<CreateWorkspaceResult> => await { id: 'xxx', name: 'xxx' };
|
||||||
|
|
||||||
|
export const getWorkspaces = async (
|
||||||
|
parameters: GetWorkspaces
|
||||||
|
): Promise<GetWorkspacesResult> =>
|
||||||
|
await {
|
||||||
|
workspaces: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWorkspace = async (
|
||||||
|
parameters: GetWorkspace
|
||||||
|
): Promise<GetWorkspaceResult> =>
|
||||||
|
await {
|
||||||
|
workspace: {
|
||||||
|
created_at: 0,
|
||||||
|
id: '',
|
||||||
|
member_count: 1,
|
||||||
|
public: true,
|
||||||
|
type: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const putBlob = async (parameters: PutBlob): Promise<string> =>
|
||||||
|
await 'path/xxx';
|
||||||
|
|
||||||
|
export const getBlob = async (parameters: GetBlob): Promise<number[]> =>
|
||||||
|
await [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will create a private workspace too.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createUser = async (parameters: CreateUser): Promise<User> =>
|
||||||
|
await {
|
||||||
|
created_at: 0,
|
||||||
|
id: '1',
|
||||||
|
email: 'xxx@xxx.xxx',
|
||||||
|
name: 'xxx',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUser = async (parameters: GetUserParameters): Promise<User> =>
|
||||||
|
await {
|
||||||
|
created_at: 0,
|
||||||
|
id: '1',
|
||||||
|
email: 'xxx@xxx.xxx',
|
||||||
|
name: 'xxx',
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { WorkspaceUnitCollection } from '../../../workspace-unit-collection.js';
|
||||||
|
import { TauriIPCProvider } from '../index.js';
|
||||||
|
import { MessageCenter } from '../../../message/index.js';
|
||||||
|
import * as ipcMethods from './mock-apis.js';
|
||||||
|
|
||||||
|
import 'fake-indexeddb/auto';
|
||||||
|
|
||||||
|
test.describe.serial('tauri-ipc provider', async () => {
|
||||||
|
const workspaceMetaCollection = new WorkspaceUnitCollection();
|
||||||
|
const provider = new TauriIPCProvider({
|
||||||
|
workspaces: workspaceMetaCollection.createScope(),
|
||||||
|
messageCenter: new MessageCenter(),
|
||||||
|
});
|
||||||
|
provider.init(ipcMethods);
|
||||||
|
|
||||||
|
const workspaceName = 'workspace-test';
|
||||||
|
let workspaceId: string | undefined;
|
||||||
|
|
||||||
|
test('create workspace', async () => {
|
||||||
|
const workspaceUnit = await provider.createWorkspace({
|
||||||
|
name: workspaceName,
|
||||||
|
});
|
||||||
|
workspaceId = workspaceUnit?.id;
|
||||||
|
|
||||||
|
expect(workspaceMetaCollection.workspaces.length).toEqual(1);
|
||||||
|
expect(workspaceMetaCollection.workspaces[0].name).toEqual(workspaceName);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('workspace list', async () => {
|
||||||
|
const workspacesMetaCollection1 = new WorkspaceUnitCollection();
|
||||||
|
const provider1 = new TauriIPCProvider({
|
||||||
|
workspaces: workspacesMetaCollection1.createScope(),
|
||||||
|
messageCenter: new MessageCenter(),
|
||||||
|
});
|
||||||
|
provider1.init(ipcMethods);
|
||||||
|
|
||||||
|
await provider1.getUserInfo();
|
||||||
|
await provider1.loadWorkspaces();
|
||||||
|
expect(workspacesMetaCollection1.workspaces.length).toEqual(0);
|
||||||
|
await provider1.createWorkspace({
|
||||||
|
name: workspaceName,
|
||||||
|
});
|
||||||
|
expect(workspacesMetaCollection1.workspaces[0].name).toEqual(workspaceName);
|
||||||
|
expect(workspacesMetaCollection1.workspaces[0].id).toEqual(workspaceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update workspace', async () => {
|
||||||
|
await provider.updateWorkspaceMeta(workspaceId!, {
|
||||||
|
name: '1111',
|
||||||
|
});
|
||||||
|
expect(workspaceMetaCollection.workspaces[0].name).toEqual('1111');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { BlobSyncState } from '@blocksuite/store';
|
||||||
|
import * as ipcMethods from '../ipc/methods.js';
|
||||||
|
import { Signal } from '@blocksuite/store';
|
||||||
|
import type {
|
||||||
|
BlobProvider,
|
||||||
|
BlobSyncStateChangeEvent,
|
||||||
|
BlobId,
|
||||||
|
BlobURL,
|
||||||
|
} from '@blocksuite/store/dist/persistence/blob/types.js';
|
||||||
|
|
||||||
|
export class IPCBlobProvider implements BlobProvider {
|
||||||
|
#ipc = ipcMethods;
|
||||||
|
|
||||||
|
#workspace: string;
|
||||||
|
|
||||||
|
readonly signals = {
|
||||||
|
onBlobSyncStateChange: new Signal<BlobSyncStateChangeEvent>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
private constructor(workspace: string) {
|
||||||
|
this.#workspace = workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async init(workspace: string): Promise<IPCBlobProvider> {
|
||||||
|
const provider = new IPCBlobProvider(workspace);
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
get uploading() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get blobs() {
|
||||||
|
// TODO: implement blob list in Octobase
|
||||||
|
return Promise.resolve([]) as Promise<string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(id: BlobId): Promise<BlobURL | null> {
|
||||||
|
const blobArray = await this.#ipc.getBlob({
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
// Make a Blob from the bytes
|
||||||
|
const blob = new Blob([new Uint8Array(blobArray)], { type: 'image/bmp' });
|
||||||
|
this.signals.onBlobSyncStateChange.emit({
|
||||||
|
id,
|
||||||
|
state: BlobSyncState.Success,
|
||||||
|
});
|
||||||
|
return window.URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(blob: Blob): Promise<BlobId> {
|
||||||
|
// TODO: skip if already has
|
||||||
|
const blobID = await this.#ipc.putBlob({
|
||||||
|
blob: Array.from(new Uint8Array(await blob.arrayBuffer())),
|
||||||
|
});
|
||||||
|
this.signals.onBlobSyncStateChange.emit({
|
||||||
|
id: blobID,
|
||||||
|
state: BlobSyncState.Success,
|
||||||
|
});
|
||||||
|
return blobID;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: BlobId): Promise<void> {
|
||||||
|
// TODO: implement blob delete in Octobase
|
||||||
|
}
|
||||||
|
|
||||||
|
async clear(): Promise<void> {
|
||||||
|
// TODO: implement blob clear in Octobase, use workspace id #workspace
|
||||||
|
}
|
||||||
|
}
|
||||||
194
packages/data-center/src/provider/tauri-ipc/index.ts
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import * as Y from 'yjs';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
import { LocalProvider } from '../local/index.js';
|
||||||
|
import type { IPCMethodsType } from './ipc/methods.js';
|
||||||
|
import {
|
||||||
|
CreateWorkspaceInfoParams,
|
||||||
|
ProviderConstructorParams,
|
||||||
|
} from '../base.js';
|
||||||
|
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
|
||||||
|
import { IPCBlobProvider } from './blocksuite-provider/blob.js';
|
||||||
|
import type { WorkspaceUnit } from 'src/workspace-unit.js';
|
||||||
|
import { createWorkspaceUnit, loadWorkspaceUnit } from '../local/utils.js';
|
||||||
|
import { WorkspaceWithPermission } from './ipc/types/workspace.js';
|
||||||
|
import { applyUpdate } from '../../utils/index.js';
|
||||||
|
import { User } from 'src/types/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init - createUser - create first workspace and ydoc - loadWorkspace - return the first workspace - wrapWorkspace - #initDocFromIPC - applyUpdate - on('update') - updateYDocument
|
||||||
|
*
|
||||||
|
* (init - createUser - error) loadWorkspace - return the first workspace - wrapWorkspace - #initDocFromIPC - applyUpdate - on('update') - updateYDocument
|
||||||
|
*/
|
||||||
|
export class TauriIPCProvider extends LocalProvider {
|
||||||
|
public id = 'tauri-ipc';
|
||||||
|
static defaultUserEmail = 'xxx@xx.xx';
|
||||||
|
/**
|
||||||
|
* // TODO: We only have one user in this version of app client. But may support switch user later.
|
||||||
|
*/
|
||||||
|
#userID?: string;
|
||||||
|
#ipc: IPCMethodsType | undefined;
|
||||||
|
|
||||||
|
constructor(params: ProviderConstructorParams) {
|
||||||
|
super(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(ipc?: IPCMethodsType) {
|
||||||
|
if (ipc) {
|
||||||
|
this.#ipc = ipc;
|
||||||
|
} else {
|
||||||
|
this.#ipc = await import('./ipc/methods.js');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const user = await this.#ipc?.getUser({
|
||||||
|
email: TauriIPCProvider.defaultUserEmail,
|
||||||
|
});
|
||||||
|
this.#userID = user.id;
|
||||||
|
} catch (error) {
|
||||||
|
// maybe user not existed, we create a default user if we don't have one.
|
||||||
|
try {
|
||||||
|
const user = await this.#ipc?.createUser({
|
||||||
|
email: TauriIPCProvider.defaultUserEmail,
|
||||||
|
name: 'xxx',
|
||||||
|
password: 'xxx',
|
||||||
|
avatar_url: '',
|
||||||
|
});
|
||||||
|
this.#userID = user.id;
|
||||||
|
} catch (error) {
|
||||||
|
// maybe user existed, which can be omited
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get auth user info
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async getUserInfo(): Promise<User | undefined> {
|
||||||
|
const user = await this.#ipc?.getUser({
|
||||||
|
email: TauriIPCProvider.defaultUserEmail,
|
||||||
|
});
|
||||||
|
if (user?.name !== undefined) {
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
avatar: user?.avatar_url || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #initDocFromIPC(
|
||||||
|
workspaceID: string,
|
||||||
|
blocksuiteWorkspace: BlocksuiteWorkspace
|
||||||
|
) {
|
||||||
|
this._logger(`Loading ${workspaceID}...`);
|
||||||
|
const result = await this.#ipc?.getYDocument({ id: workspaceID });
|
||||||
|
if (result) {
|
||||||
|
const updates = result.updates.map(
|
||||||
|
binaryUpdate => new Uint8Array(binaryUpdate)
|
||||||
|
);
|
||||||
|
|
||||||
|
const mergedUpdate = Y.mergeUpdates(updates);
|
||||||
|
await applyUpdate(blocksuiteWorkspace, mergedUpdate);
|
||||||
|
this._logger(`Loaded: ${workspaceID}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #connectDocToIPC(
|
||||||
|
workspaceID: string,
|
||||||
|
blocksuiteWorkspace: BlocksuiteWorkspace
|
||||||
|
) {
|
||||||
|
this._logger(`Connecting yDoc for ${workspaceID}...`);
|
||||||
|
blocksuiteWorkspace.doc.on('update', async (update: Uint8Array) => {
|
||||||
|
try {
|
||||||
|
const binary = Y.encodeStateAsUpdate(blocksuiteWorkspace.doc);
|
||||||
|
const success = await this.#ipc?.updateYDocument({
|
||||||
|
update: Array.from(binary),
|
||||||
|
id: workspaceID,
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(`YDoc update failed, id: ${workspaceID}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// TODO: write error log to disk, and add button to open them in settings panel
|
||||||
|
console.error("#yDocument.on('update'", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async clear() {
|
||||||
|
await super.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
override async warpWorkspace(blocksuiteWorkspace: BlocksuiteWorkspace) {
|
||||||
|
const { room } = blocksuiteWorkspace;
|
||||||
|
assert(room);
|
||||||
|
|
||||||
|
(await blocksuiteWorkspace.blobs)?.setProvider(
|
||||||
|
await IPCBlobProvider.init(room)
|
||||||
|
);
|
||||||
|
await this.#initDocFromIPC(room, blocksuiteWorkspace);
|
||||||
|
await this.#connectDocToIPC(room, blocksuiteWorkspace);
|
||||||
|
|
||||||
|
return blocksuiteWorkspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async createWorkspace(
|
||||||
|
meta: CreateWorkspaceInfoParams
|
||||||
|
): Promise<WorkspaceUnit | undefined> {
|
||||||
|
this._logger('Creating client app workspace');
|
||||||
|
assert(this.#ipc);
|
||||||
|
assert(this.#userID);
|
||||||
|
const { id } = await this.#ipc.createWorkspace({
|
||||||
|
name: meta.name,
|
||||||
|
// TODO: get userID here
|
||||||
|
user_id: this.#userID,
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspaceUnit = await createWorkspaceUnit({
|
||||||
|
name: meta.name,
|
||||||
|
id,
|
||||||
|
published: false,
|
||||||
|
avatar: '',
|
||||||
|
owner: undefined,
|
||||||
|
syncMode: 'core',
|
||||||
|
memberCount: 1,
|
||||||
|
provider: this.id,
|
||||||
|
});
|
||||||
|
this._workspaces.add(workspaceUnit);
|
||||||
|
const doc = workspaceUnit?.blocksuiteWorkspace?.doc;
|
||||||
|
if (doc) {
|
||||||
|
const update = Y.encodeStateAsUpdate(doc);
|
||||||
|
const success = await this.#ipc?.updateYDocument({
|
||||||
|
update: Array.from(update),
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(`YDoc update failed, id: ${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return workspaceUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async loadWorkspaces(): Promise<WorkspaceUnit[]> {
|
||||||
|
assert(this.#ipc);
|
||||||
|
assert(this.#userID);
|
||||||
|
const { workspaces } = await this.#ipc.getWorkspaces({
|
||||||
|
user_id: this.#userID,
|
||||||
|
});
|
||||||
|
const workspaceUnits = await Promise.all(
|
||||||
|
workspaces.map((meta: WorkspaceWithPermission) => {
|
||||||
|
return loadWorkspaceUnit({
|
||||||
|
...meta,
|
||||||
|
memberCount: 1,
|
||||||
|
// TODO: load name here
|
||||||
|
name: '',
|
||||||
|
provider: this.id,
|
||||||
|
syncMode: 'all',
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this._workspaces.add(workspaceUnits);
|
||||||
|
return workspaceUnits;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
packages/data-center/src/provider/tauri-ipc/ipc/methods.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import {
|
||||||
|
GetDocumentParameter,
|
||||||
|
GetDocumentResponse,
|
||||||
|
YDocumentUpdate,
|
||||||
|
} from './types/document';
|
||||||
|
import {
|
||||||
|
CreateWorkspace,
|
||||||
|
CreateWorkspaceResult,
|
||||||
|
GetWorkspace,
|
||||||
|
GetWorkspaceResult,
|
||||||
|
GetWorkspaces,
|
||||||
|
GetWorkspacesResult,
|
||||||
|
User,
|
||||||
|
} from './types/workspace';
|
||||||
|
import { GetBlob, PutBlob } from './types/blob';
|
||||||
|
import { CreateUser, GetUserParameters } from './types/user';
|
||||||
|
|
||||||
|
export interface IPCMethodsType {
|
||||||
|
updateYDocument: typeof updateYDocument;
|
||||||
|
getYDocument: typeof getYDocument;
|
||||||
|
createWorkspace: typeof createWorkspace;
|
||||||
|
getWorkspaces: typeof getWorkspaces;
|
||||||
|
getWorkspace: typeof getWorkspace;
|
||||||
|
putBlob: typeof putBlob;
|
||||||
|
getBlob: typeof getBlob;
|
||||||
|
createUser: typeof createUser;
|
||||||
|
getUser: typeof getUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateYDocument = async (parameters: YDocumentUpdate) =>
|
||||||
|
await invoke<boolean>('update_y_document', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getYDocument = async (parameters: GetDocumentParameter) =>
|
||||||
|
await invoke<GetDocumentResponse>('get_doc', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createWorkspace = async (parameters: CreateWorkspace) =>
|
||||||
|
await invoke<CreateWorkspaceResult>('create_workspace', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getWorkspaces = async (parameters: GetWorkspaces) =>
|
||||||
|
await invoke<GetWorkspacesResult>('get_workspaces', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getWorkspace = async (parameters: GetWorkspace) =>
|
||||||
|
await invoke<GetWorkspaceResult>('get_workspace', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const putBlob = async (parameters: PutBlob) =>
|
||||||
|
await invoke<string>('put_blob', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getBlob = async (parameters: GetBlob) =>
|
||||||
|
await invoke<number[]>('get_blob', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will create a private workspace too.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createUser = async (parameters: CreateUser) =>
|
||||||
|
await invoke<User>('create_user', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getUser = async (parameters: GetUserParameters) =>
|
||||||
|
await invoke<User>('get_user', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "IBlobParameters",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["Put"],
|
||||||
|
"properties": {
|
||||||
|
"Put": {
|
||||||
|
"$ref": "#/definitions/PutBlob"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["Get"],
|
||||||
|
"properties": {
|
||||||
|
"Get": {
|
||||||
|
"$ref": "#/definitions/GetBlob"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"definitions": {
|
||||||
|
"GetBlob": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"type": ["string", "null"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PutBlob": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["blob"],
|
||||||
|
"properties": {
|
||||||
|
"blob": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"type": ["string", "null"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* This file was automatically generated by json-schema-to-typescript.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||||
|
* and run json-schema-to-typescript to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type IBlobParameters =
|
||||||
|
| {
|
||||||
|
Put: PutBlob;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
Get: GetBlob;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PutBlob {
|
||||||
|
blob: number[];
|
||||||
|
workspace_id?: string | null;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface GetBlob {
|
||||||
|
id: string;
|
||||||
|
workspace_id?: string | null;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "IDocumentParameters",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["YDocumentUpdate"],
|
||||||
|
"properties": {
|
||||||
|
"YDocumentUpdate": {
|
||||||
|
"$ref": "#/definitions/YDocumentUpdate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["CreateDocumentParameter"],
|
||||||
|
"properties": {
|
||||||
|
"CreateDocumentParameter": {
|
||||||
|
"$ref": "#/definitions/CreateDocumentParameter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["GetDocumentParameter"],
|
||||||
|
"properties": {
|
||||||
|
"GetDocumentParameter": {
|
||||||
|
"$ref": "#/definitions/GetDocumentParameter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["GetDocumentResponse"],
|
||||||
|
"properties": {
|
||||||
|
"GetDocumentResponse": {
|
||||||
|
"$ref": "#/definitions/GetDocumentResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"definitions": {
|
||||||
|
"CreateDocumentParameter": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["workspace_id", "workspace_name"],
|
||||||
|
"properties": {
|
||||||
|
"workspace_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"workspace_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetDocumentParameter": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetDocumentResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["updates"],
|
||||||
|
"properties": {
|
||||||
|
"updates": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"YDocumentUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "update"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* This file was automatically generated by json-schema-to-typescript.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||||
|
* and run json-schema-to-typescript to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type IDocumentParameters =
|
||||||
|
| {
|
||||||
|
YDocumentUpdate: YDocumentUpdate;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
CreateDocumentParameter: CreateDocumentParameter;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
GetDocumentParameter: GetDocumentParameter;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
GetDocumentResponse: GetDocumentResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface YDocumentUpdate {
|
||||||
|
id: string;
|
||||||
|
update: number[];
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface CreateDocumentParameter {
|
||||||
|
workspace_id: string;
|
||||||
|
workspace_name: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface GetDocumentParameter {
|
||||||
|
id: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface GetDocumentResponse {
|
||||||
|
updates: number[][];
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "IUserParameters",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["CreateUser"],
|
||||||
|
"properties": {
|
||||||
|
"CreateUser": {
|
||||||
|
"$ref": "#/definitions/CreateUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["User"],
|
||||||
|
"properties": {
|
||||||
|
"User": {
|
||||||
|
"$ref": "#/definitions/User"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["GetUserParameters"],
|
||||||
|
"properties": {
|
||||||
|
"GetUserParameters": {
|
||||||
|
"$ref": "#/definitions/GetUserParameters"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"definitions": {
|
||||||
|
"CreateUser": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["email", "name", "password"],
|
||||||
|
"properties": {
|
||||||
|
"avatar_url": {
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetUserParameters": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["email"],
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"User": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["created_at", "email", "id", "name"],
|
||||||
|
"properties": {
|
||||||
|
"avatar_url": {
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* This file was automatically generated by json-schema-to-typescript.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||||
|
* and run json-schema-to-typescript to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type IUserParameters =
|
||||||
|
| {
|
||||||
|
CreateUser: CreateUser;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
User: User;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
GetUserParameters: GetUserParameters;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface CreateUser {
|
||||||
|
avatar_url?: string | null;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface User {
|
||||||
|
avatar_url?: string | null;
|
||||||
|
created_at: number;
|
||||||
|
email: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface GetUserParameters {
|
||||||
|
email: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "IWorkspaceParameters",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["CreateWorkspace"],
|
||||||
|
"properties": {
|
||||||
|
"CreateWorkspace": {
|
||||||
|
"$ref": "#/definitions/CreateWorkspace"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["GetWorkspace"],
|
||||||
|
"properties": {
|
||||||
|
"GetWorkspace": {
|
||||||
|
"$ref": "#/definitions/GetWorkspace"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["GetWorkspaces"],
|
||||||
|
"properties": {
|
||||||
|
"GetWorkspaces": {
|
||||||
|
"$ref": "#/definitions/GetWorkspaces"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["GetWorkspaceResult"],
|
||||||
|
"properties": {
|
||||||
|
"GetWorkspaceResult": {
|
||||||
|
"$ref": "#/definitions/GetWorkspaceResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["GetWorkspacesResult"],
|
||||||
|
"properties": {
|
||||||
|
"GetWorkspacesResult": {
|
||||||
|
"$ref": "#/definitions/GetWorkspacesResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["UpdateWorkspace"],
|
||||||
|
"properties": {
|
||||||
|
"UpdateWorkspace": {
|
||||||
|
"$ref": "#/definitions/UpdateWorkspace"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["CreateWorkspaceResult"],
|
||||||
|
"properties": {
|
||||||
|
"CreateWorkspaceResult": {
|
||||||
|
"$ref": "#/definitions/CreateWorkspaceResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"definitions": {
|
||||||
|
"CreateWorkspace": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name", "user_id"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "only set name, avatar is update in datacenter to yDoc directly",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CreateWorkspaceResult": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "name"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetWorkspace": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetWorkspaceResult": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["workspace"],
|
||||||
|
"properties": {
|
||||||
|
"workspace": {
|
||||||
|
"$ref": "#/definitions/WorkspaceDetail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetWorkspaces": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["user_id"],
|
||||||
|
"properties": {
|
||||||
|
"user_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetWorkspacesResult": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["workspaces"],
|
||||||
|
"properties": {
|
||||||
|
"workspaces": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/WorkspaceWithPermission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PermissionType": {
|
||||||
|
"type": "integer",
|
||||||
|
"enum": [0, 1, 10, 99]
|
||||||
|
},
|
||||||
|
"UpdateWorkspace": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "public"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"User": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["created_at", "email", "id", "name"],
|
||||||
|
"properties": {
|
||||||
|
"avatar_url": {
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorkspaceDetail": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["created_at", "id", "member_count", "public", "type"],
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"member_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/definitions/WorkspaceType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WorkspaceType": {
|
||||||
|
"type": "integer",
|
||||||
|
"enum": [0, 1]
|
||||||
|
},
|
||||||
|
"WorkspaceWithPermission": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["created_at", "id", "permission", "public", "type"],
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"$ref": "#/definitions/PermissionType"
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/definitions/WorkspaceType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* This file was automatically generated by json-schema-to-typescript.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||||
|
* and run json-schema-to-typescript to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type IWorkspaceParameters =
|
||||||
|
| {
|
||||||
|
CreateWorkspace: CreateWorkspace;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
GetWorkspace: GetWorkspace;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
GetWorkspaces: GetWorkspaces;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
GetWorkspaceResult: GetWorkspaceResult;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
GetWorkspacesResult: GetWorkspacesResult;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
UpdateWorkspace: UpdateWorkspace;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
CreateWorkspaceResult: CreateWorkspaceResult;
|
||||||
|
};
|
||||||
|
export type WorkspaceType = 0 | 1;
|
||||||
|
export type PermissionType = 0 | 1 | 10 | 99;
|
||||||
|
|
||||||
|
export interface CreateWorkspace {
|
||||||
|
/**
|
||||||
|
* only set name, avatar is update in datacenter to yDoc directly
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
user_id: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface GetWorkspace {
|
||||||
|
id: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface GetWorkspaces {
|
||||||
|
user_id: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface GetWorkspaceResult {
|
||||||
|
workspace: WorkspaceDetail;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface WorkspaceDetail {
|
||||||
|
created_at: number;
|
||||||
|
id: string;
|
||||||
|
member_count: number;
|
||||||
|
owner?: User | null;
|
||||||
|
public: boolean;
|
||||||
|
type: WorkspaceType;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface User {
|
||||||
|
avatar_url?: string | null;
|
||||||
|
created_at: number;
|
||||||
|
email: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface GetWorkspacesResult {
|
||||||
|
workspaces: WorkspaceWithPermission[];
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface WorkspaceWithPermission {
|
||||||
|
created_at: number;
|
||||||
|
id: string;
|
||||||
|
permission: PermissionType;
|
||||||
|
public: boolean;
|
||||||
|
type: WorkspaceType;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface UpdateWorkspace {
|
||||||
|
id: number;
|
||||||
|
public: boolean;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
export interface CreateWorkspaceResult {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
@@ -11,6 +11,13 @@ import { getLogger } from '../logger';
|
|||||||
// provider: string;
|
// provider: string;
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
__TAURI_IPC__: Function;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
53
patches/@tauri-apps__api@1.2.0.patch
Normal file
1925
pnpm-lock.yaml
generated
@@ -1,3 +1,5 @@
|
|||||||
packages:
|
packages:
|
||||||
# all packages in direct subdirs of packages/
|
# all packages in direct subdirs of packages/
|
||||||
- 'packages/*'
|
- 'packages/*'
|
||||||
|
# app folder is on top level because of its importance
|
||||||
|
- 'client-app'
|
||||||
|
|||||||