chore: web and tauri project (#4996)
* chore: web and tauri project fix: clippy * fix: update version
113
.github/workflows/tauri2_ci.yaml
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
name: Tauri2-CI
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/tauri2_ci.yaml"
|
||||
- "frontend/rust-lib/**"
|
||||
- "frontend/appflowy_web_app/**"
|
||||
- "frontend/resources/**"
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
RUST_TOOLCHAIN: "1.75"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tauri-build:
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-20.04 ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
env:
|
||||
CI: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Maximize build space (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo docker image prune --all --force
|
||||
sudo rm -rf /opt/hostedtoolcache/codeQL
|
||||
sudo rm -rf ${GITHUB_WORKSPACE}/.git
|
||||
sudo rm -rf $ANDROID_HOME/ndk
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./frontend/appflowy_web_app/src-tauri -> target"
|
||||
|
||||
- name: Node_modules cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: frontend/appflowy_web_app/node_modules
|
||||
key: node-modules-${{ runner.os }}
|
||||
|
||||
- name: install dependencies (windows only)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force duckscript_cli
|
||||
vcpkg integrate install
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
working-directory: frontend
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: install cargo-make
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force cargo-make
|
||||
cargo make appflowy-tauri-deps-tools
|
||||
|
||||
- name: install frontend dependencies
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
mkdir dist
|
||||
pnpm install
|
||||
cd src-tauri && cargo build
|
||||
|
||||
- name: test and lint
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm run lint:tauri
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tauriScript: pnpm tauri
|
||||
projectPath: frontend/appflowy_web_app
|
||||
args: "--debug"
|
61
.github/workflows/web2_ci.yaml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
name: Web2-CI
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/web2_ci.yaml"
|
||||
- "frontend/appflowy_web_app/**"
|
||||
- "frontend/resources/**"
|
||||
env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
web-build:
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Maximize build space (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo docker image prune --all --force
|
||||
sudo rm -rf /opt/hostedtoolcache/codeQL
|
||||
sudo rm -rf ${GITHUB_WORKSPACE}/.git
|
||||
sudo rm -rf $ANDROID_HOME/ndk
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Node_modules cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: frontend/appflowy_web_app/node_modules
|
||||
key: node-modules-${{ runner.os }}
|
||||
- name: install frontend dependencies
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm install
|
||||
- name: test and lint
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm run lint
|
||||
- name: build
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm run build
|
2
.gitignore
vendored
@ -40,3 +40,5 @@ frontend/package
|
||||
frontend/*.deb
|
||||
|
||||
**/Cargo.toml.bak
|
||||
|
||||
**/.cargo/**
|
@ -51,6 +51,7 @@ FLUTTER_FLOWY_SDK_PATH = "appflowy_flutter/packages/appflowy_backend"
|
||||
TAURI_BACKEND_SERVICE_PATH = "appflowy_tauri/src/services/backend"
|
||||
WEB_BACKEND_SERVICE_PATH = "appflowy_web/src/services/backend"
|
||||
WEB_LIB_PATH = "appflowy_web/wasm-libs/af-wasm"
|
||||
TAURI_APP_BACKEND_SERVICE_PATH = "appflowy_web_app/src/application/services/tauri-services/backend"
|
||||
# Test default config
|
||||
TEST_CRATE_TYPE = "cdylib"
|
||||
TEST_LIB_EXT = "dylib"
|
||||
|
6
frontend/appflowy_web_app/.eslintignore
Normal file
@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
src-tauri/
|
||||
.eslintrc.cjs
|
||||
tsconfig.json
|
||||
**/backend/**
|
6
frontend/appflowy_web_app/.eslintignore.web
Normal file
@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
src-tauri/
|
||||
.eslintrc.cjs
|
||||
tsconfig.json
|
||||
src/application/services/tauri-services/
|
73
frontend/appflowy_web_app/.eslintrc.cjs
Normal file
@ -0,0 +1,73 @@
|
||||
module.exports = {
|
||||
// https://eslint.org/docs/latest/use/configure/configuration-files
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: __dirname,
|
||||
extraFileExtensions: ['.json'],
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'react-hooks'],
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||
'@typescript-eslint/no-empty-function': 'error',
|
||||
'@typescript-eslint/no-empty-interface': 'error',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/no-namespace': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
'@typescript-eslint/prefer-for-of': 'error',
|
||||
'@typescript-eslint/triple-slash-reference': 'error',
|
||||
'@typescript-eslint/unified-signatures': 'error',
|
||||
'no-shadow': 'off',
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'constructor-super': 'error',
|
||||
eqeqeq: ['error', 'always'],
|
||||
'no-cond-assign': 'error',
|
||||
'no-duplicate-case': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-empty': [
|
||||
'error',
|
||||
{
|
||||
allowEmptyCatch: true,
|
||||
},
|
||||
],
|
||||
'no-invalid-this': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-param-reassign': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-throw-literal': 'error',
|
||||
'no-unsafe-finally': 'error',
|
||||
'no-unused-labels': 'error',
|
||||
'no-var': 'error',
|
||||
'no-void': 'off',
|
||||
'prefer-const': 'error',
|
||||
'prefer-spread': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'padding-line-between-statements': [
|
||||
'error',
|
||||
{ blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
|
||||
{ blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] },
|
||||
{ blankLine: 'always', prev: 'import', next: '*' },
|
||||
{ blankLine: 'any', prev: 'import', next: 'import' },
|
||||
{ blankLine: 'always', prev: 'block-like', next: '*' },
|
||||
{ blankLine: 'always', prev: 'block', next: '*' },
|
||||
|
||||
],
|
||||
},
|
||||
ignorePatterns: ['src/**/*.test.ts', '**/__tests__/**/*.json', 'package.json'],
|
||||
};
|
32
frontend/appflowy_web_app/.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist/**
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
src/@types/translations/*.json
|
||||
|
||||
|
||||
src/application/services/tauri-services/backend/models/
|
||||
src/application/services/tauri-services/backend/events/
|
||||
|
||||
.env
|
20
frontend/appflowy_web_app/.prettierrc.cjs
Normal file
@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
arrowParens: 'always',
|
||||
bracketSpacing: true,
|
||||
endOfLine: 'lf',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
insertPragma: false,
|
||||
jsxBracketSameLine: false,
|
||||
jsxSingleQuote: true,
|
||||
printWidth: 121,
|
||||
plugins: [require('prettier-plugin-tailwindcss')],
|
||||
proseWrap: 'preserve',
|
||||
quoteProps: 'as-needed',
|
||||
requirePragma: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'es5',
|
||||
useTabs: false,
|
||||
vueIndentScriptAndStyle: false,
|
||||
};
|
201
frontend/appflowy_web_app/README.md
Normal file
@ -0,0 +1,201 @@
|
||||
<div align="center">
|
||||
|
||||
<h1><code>AppFlowy Web Project</code></h1>
|
||||
|
||||
<div>Welcome to the AppFlowy Web Project, a robust and versatile platform designed to bring the innovative features of
|
||||
AppFlowy to the web. This project uniquely supports running as a desktop application via Tauri, and offers web
|
||||
interfaces powered by WebAssembly (WASM). Dive into an exceptional development experience with high performance and
|
||||
extensive capabilities.</div>
|
||||
|
||||
</div>
|
||||
|
||||
## 🐑 Features
|
||||
|
||||
- **Cross-Platform Compatibility**: Seamlessly run on desktop environments with Tauri, and on any web browser through
|
||||
WASM.
|
||||
- **High Performance**: Leverage the speed and efficiency of WebAssembly for your web interfaces.
|
||||
- **Tauri Integration**: Build lightweight, secure, and efficient desktop applications.
|
||||
- **Flexible Development**: Utilize a wide range of AppFlowy's functionalities in your web or desktop projects.
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### 🛠️ Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following installed:
|
||||
|
||||
- Node.js (v14 or later)
|
||||
- Rust (latest stable version)
|
||||
- Tauri prerequisites for your operating system
|
||||
- PNPM (8.5.0)
|
||||
|
||||
### 🏗️ Installation
|
||||
|
||||
#### Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/AppFlowy-IO/AppFlowy
|
||||
```
|
||||
|
||||
#### 🌐 Install the frontend dependencies:
|
||||
|
||||
```bash
|
||||
cd frontend/appflowy_web_app
|
||||
pnpm install
|
||||
```
|
||||
|
||||
#### 🖥️ Desktop Application (Tauri) (Optional)
|
||||
|
||||
> **Note**: if you want to run the web app in the browser, skip this step
|
||||
|
||||
- Follow the instructions [here](https://tauri.app/v1/guides/getting-started/prerequisites/) to install Tauri
|
||||
|
||||
##### Windows and Linux Prerequisites
|
||||
|
||||
###### Windows only
|
||||
|
||||
- Install the Duckscript CLI and vcpkg
|
||||
|
||||
```bash
|
||||
cargo install --force duckscript_cli
|
||||
vcpkg integrate install
|
||||
```
|
||||
|
||||
###### Linux only
|
||||
|
||||
- Install the required dependencies
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
```
|
||||
|
||||
- **Get error**: failed to run custom build command for librocksdb-sys v6.11.4
|
||||
|
||||
```bash
|
||||
sudo apt install clang
|
||||
```
|
||||
|
||||
##### Install Tauri Dependencies
|
||||
|
||||
- Install cargo-make
|
||||
|
||||
```bash
|
||||
cargo install --force cargo-make
|
||||
```
|
||||
|
||||
|
||||
- Install AppFlowy dev tools
|
||||
|
||||
```bash
|
||||
# install development tools
|
||||
# make sure you are in the root directory of the project
|
||||
cd frontend
|
||||
cargo make appflowy-tauri-deps-tools
|
||||
```
|
||||
|
||||
- Build the service/dependency
|
||||
|
||||
```bash
|
||||
# make sure you are in the root directory of the project
|
||||
cd frontend/appflowy_web_app
|
||||
mkdir dist
|
||||
cd src-tauri
|
||||
cargo build
|
||||
```
|
||||
|
||||
### 🚀 Running the Application
|
||||
|
||||
#### 🌐 Web Application
|
||||
|
||||
- Run the web application
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
```
|
||||
- Open your browser and navigate to `http://localhost:3000`, You can now interact with the AppFlowy web application
|
||||
|
||||
#### 🖥️ Desktop Application (Tauri)
|
||||
|
||||
**Ensure close web application before running the desktop application**
|
||||
|
||||
- Run the desktop application
|
||||
|
||||
```bash
|
||||
pnpm run tauri:dev
|
||||
```
|
||||
- The AppFlowy desktop application will open, and you can interact with it
|
||||
|
||||
### 🛠️ Development
|
||||
|
||||
#### How to add or modify i18n keys
|
||||
|
||||
- Modify the i18n files in `frontend/resources/translations/en.json` to add or modify i18n keys
|
||||
- Run the following command to update the i18n keys in the application
|
||||
|
||||
```bash
|
||||
pnpm run sync:i18n
|
||||
```
|
||||
|
||||
#### How to modify the theme
|
||||
|
||||
Don't modify the theme file in `frontend/appflowy_web_app/src/styles/variables` directly)
|
||||
|
||||
- Modify the theme file in `frontend/appflowy_web_app/style-dictionary/tokens/base.json( or dark.json or light.json)` to
|
||||
add or modify theme keys
|
||||
- Run the following command to update the theme in the application
|
||||
|
||||
```bash
|
||||
pnpm run css:variables
|
||||
```
|
||||
|
||||
#### How to add or modify the environment variables
|
||||
|
||||
- Modify the environment file in `frontend/appflowy_web_app/.env` to add or modify environment variables
|
||||
|
||||
#### How to create symlink for the @appflowyinc/client-api-wasm in local development
|
||||
|
||||
- Run the following command to create a symlink for the @appflowyinc/client-api-wasm
|
||||
|
||||
```bash
|
||||
# ensure you are in the frontend/appflowy_web_app directory
|
||||
|
||||
pnpm run link:client-api $source_path $target_path
|
||||
|
||||
# Example
|
||||
# pnpm run link:client-api ../../../AppFlowy-Cloud/libs/client-api-wasm/pkg ./node_modules/@appflowyinc/client-api-wasm
|
||||
```
|
||||
|
||||
### 📝 About the Project
|
||||
|
||||
#### 📁 Directory Structure
|
||||
|
||||
- `frontend/appflowy_web_app`: Contains the web application source code
|
||||
- `frontend/appflowy_web_app/src`: Contains the app entry point and the source code
|
||||
- `frontend/appflowy_web_app/src/components`: Contains the react components
|
||||
- `frontend/appflowy_web_app/src/styles`: Contains the styles for the application
|
||||
- `frontend/appflowy_web_app/src/utils`: Contains the utility functions
|
||||
- `frontend/appflowy_web_app/src/i18n`: Contains the i18n files
|
||||
- `frontend/appflowy_web_app/src/assets`: Contains the assets for the application
|
||||
- `frontend/appflowy_web_app/src/store`: Contains the redux store
|
||||
- `frontend/appflowy_web_app/src/@types`: Contains the typescript types
|
||||
- `frontend/appflowy_web_app/src/applications/services`: Contains the services for the application. In vite.config.ts,
|
||||
we have defined the alias for the services directory for different environments(Tauri/Web)
|
||||
```typescript
|
||||
resolve: {
|
||||
alias: [
|
||||
// ...
|
||||
{
|
||||
find: '$client-services',
|
||||
replacement: process.env.TAURI_MODE
|
||||
? `${__dirname}/src/application/services/tauri-services`
|
||||
: `${__dirname}/src/application/services/js-services`,
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 🧪 Testing
|
||||
|
||||
> To be Continued...
|
||||
|
||||
|
16
frontend/appflowy_web_app/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/appflowy.svg"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||
</style>
|
||||
<title>AppFlowy</title>
|
||||
</head>
|
||||
<body id="body">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
131
frontend/appflowy_web_app/package.json
Normal file
@ -0,0 +1,131 @@
|
||||
{
|
||||
"name": "appflowy_web_app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm run sync:i18n && vite",
|
||||
"dev:tauri": "TAURI_MODE=true pnpm run sync:i18n && vite",
|
||||
"build": "vite build",
|
||||
"build:tauri": "TAURI_MODE=true vite build",
|
||||
"lint:tauri": "TAURI_MODE=true pnpm run sync:i18n && tsc --noEmit && eslint --ext .js,.ts,.tsx . --ignore-path .eslintignore",
|
||||
"lint": "pnpm run sync:i18n && tsc --noEmit --project tsconfig.web.json && eslint --ext .js,.ts,.tsx . --ignore-path .eslintignore.web",
|
||||
"preview": "vite preview --port 8080",
|
||||
"tauri:dev": "tauri dev",
|
||||
"css:variables": "node style-dictionary/config.cjs",
|
||||
"sync:i18n": "node scripts/i18n.cjs",
|
||||
"link:client-api": "rm -rf node_modules/.vite && node scripts/create-symlink.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appflowyinc/client-api-wasm": "^0.0.2",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@mui/icons-material": "^5.11.11",
|
||||
"@mui/material": "^5.11.12",
|
||||
"@mui/system": "^5.14.4",
|
||||
"@mui/x-date-pickers-pro": "^6.18.2",
|
||||
"@reduxjs/toolkit": "2.0.0",
|
||||
"@slate-yjs/core": "^1.0.2",
|
||||
"@tauri-apps/api": "^1.5.3",
|
||||
"@types/react-swipeable-views": "^0.13.4",
|
||||
"axios": "^1.6.8",
|
||||
"dayjs": "^1.11.9",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"events": "^3.3.0",
|
||||
"google-protobuf": "^3.15.12",
|
||||
"i18next": "^22.4.10",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"i18next-resources-to-backend": "^1.1.4",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"jest": "^29.5.0",
|
||||
"js-base64": "^3.7.5",
|
||||
"katex": "^0.16.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"protoc-gen-ts": "0.8.7",
|
||||
"quill": "^1.3.7",
|
||||
"quill-delta": "^5.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-big-calendar": "^1.8.5",
|
||||
"react-color": "^2.19.3",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-datepicker": "^4.23.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-i18next": "^14.1.0",
|
||||
"react-katex": "^3.0.1",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-swipeable-views": "^0.14.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-virtualized-auto-sizer": "^1.0.20",
|
||||
"react-vtree": "^2.0.4",
|
||||
"react-window": "^1.8.10",
|
||||
"react18-input-otp": "^1.1.2",
|
||||
"redux": "^4.2.1",
|
||||
"rxjs": "^7.8.0",
|
||||
"sass": "^1.70.0",
|
||||
"slate": "^0.101.4",
|
||||
"slate-history": "^0.100.0",
|
||||
"slate-react": "^0.101.3",
|
||||
"ts-results": "^3.3.0",
|
||||
"unsplash-js": "^7.0.19",
|
||||
"utf8": "^3.0.0",
|
||||
"valtio": "^1.12.1",
|
||||
"vite-plugin-wasm": "^3.3.0",
|
||||
"yjs": "^13.5.51"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/plugin-svgo": "^8.0.1",
|
||||
"@tauri-apps/cli": "^1.5.11",
|
||||
"@types/google-protobuf": "^3.15.12",
|
||||
"@types/is-hotkey": "^0.1.7",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/katex": "^0.16.0",
|
||||
"@types/lodash-es": "^4.17.11",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/quill": "^2.0.10",
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-beautiful-dnd": "^13.1.3",
|
||||
"@types/react-color": "^3.0.6",
|
||||
"@types/react-custom-scrollbars": "^4.0.13",
|
||||
"@types/react-datepicker": "^4.19.3",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@types/react-katex": "^3.0.0",
|
||||
"@types/react-transition-group": "^4.4.6",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/utf8": "^3.0.1",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"babel-jest": "^29.6.2",
|
||||
"chalk": "^4.1.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"jest-environment-jsdom": "^29.6.2",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "2.8.4",
|
||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||
"style-dictionary": "^3.9.2",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsconfig-paths-jest": "^0.0.1",
|
||||
"typescript": "4.9.5",
|
||||
"uuid": "^9.0.0",
|
||||
"vite": "^5.2.0",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vite-plugin-terminal": "^1.2.0"
|
||||
}
|
||||
}
|
7648
frontend/appflowy_web_app/pnpm-lock.yaml
Normal file
6
frontend/appflowy_web_app/postcss.config.cjs
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
38
frontend/appflowy_web_app/public/appflowy.svg
Normal file
@ -0,0 +1,38 @@
|
||||
<svg width='100%' height='100%' viewBox='0 0 41 40' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M39.9564 24.0195C38.8098 30.1683 33.7828 35.5321 28.0061 38.5411C27.3005 38.9336 26.4627 39.1516 25.6689 39.1952H37.9279C39.1185 39.1952 39.9564 38.323 39.9564 37.2328V24.0195Z'
|
||||
fill='#F7931E'
|
||||
/>
|
||||
<path
|
||||
d='M15.4381 12.1576C15.2617 12.2884 15.0853 12.4192 14.9089 12.55C11.9103 14.6432 2.82634 21.3589 0.753788 18.4371C-1.27467 15.6026 0.886079 7.57868 6.08952 3.69755C6.17771 3.61033 6.31 3.56672 6.3982 3.4795C12.0867 -0.48885 16.32 0.078058 18.3926 2.95621C20.3328 5.65992 18.1721 9.93353 15.4381 12.1576Z'
|
||||
fill='#8427E0'
|
||||
/>
|
||||
<path
|
||||
d='M33.8715 36.098C33.7833 36.1852 33.6951 36.2288 33.5628 36.316C27.8743 40.2844 23.641 39.7175 21.5684 36.8393C19.6282 34.1356 21.7889 29.862 24.5229 27.638C24.6993 27.5072 24.8757 27.3763 25.0521 27.2455C28.0507 25.1959 37.1347 18.4366 39.1631 21.3584C41.2357 24.1929 39.119 32.2169 33.8715 36.098Z'
|
||||
fill='#FFBD00'
|
||||
/>
|
||||
<path
|
||||
d='M17.9954 38.8459C15.085 40.8955 6.70658 38.6715 2.87014 33.264C2.78195 33.1768 2.69376 33.046 2.64966 32.9588C-1.09858 27.5078 -0.481224 23.4086 2.38508 21.4462C5.20728 19.4838 9.61698 21.7515 11.8218 24.586C11.91 24.7168 11.9982 24.804 12.0864 24.9349C14.159 27.8566 20.9499 36.8399 17.9954 38.8459Z'
|
||||
fill='#E3006D'
|
||||
/>
|
||||
<path
|
||||
d='M15.4385 12.1576C11.3816 13.9455 2.73857 17.6086 1.45976 14.6432C0.357338 12.1576 2.3858 7.09899 6.08994 3.69755C6.17814 3.61033 6.31043 3.56672 6.39862 3.4795C12.0871 -0.48885 16.3204 0.078058 18.393 2.95621C20.3333 5.65992 18.1725 9.93353 15.4385 12.1576Z'
|
||||
fill='#9327FF'
|
||||
/>
|
||||
<path
|
||||
d='M37.6624 18.3955C34.8402 20.3579 30.4305 18.0903 28.2257 15.2557C28.1375 15.1249 28.0493 15.0377 27.9611 14.9069C25.8444 11.9415 19.0535 2.95819 21.9639 0.952211C24.8743 -1.09738 33.2968 1.12664 37.1333 6.53407C37.2215 6.6649 37.3096 6.75211 37.3978 6.88294C41.102 12.334 40.5287 16.3895 37.6624 18.3955Z'
|
||||
fill='#00B5FF'
|
||||
/>
|
||||
<path
|
||||
d='M37.6628 18.3934C34.8406 20.3557 30.4309 18.0881 28.2261 15.2536C26.4181 11.1108 22.9344 2.95603 25.8448 1.73499C28.4906 0.601179 33.9587 2.86881 37.4423 6.88077C41.1024 12.3318 40.5291 16.3874 37.6628 18.3934Z'
|
||||
fill='#00C8FF'
|
||||
/>
|
||||
<path
|
||||
d='M33.8715 36.0986C33.7833 36.1858 33.6951 36.2294 33.5628 36.3166C27.8743 40.285 23.641 39.7181 21.5684 36.8399C19.6282 34.1362 21.7889 29.8626 24.5229 27.6386C28.5799 25.8506 37.2229 22.1875 38.5017 25.1529C39.6482 27.6386 37.6197 32.6971 33.8715 36.0986Z'
|
||||
fill='#FFCE00'
|
||||
/>
|
||||
<path
|
||||
d='M14.2031 38.061C11.5572 39.1948 6.08922 36.9708 2.64966 32.9588C-1.09858 27.5078 -0.481224 23.4086 2.38508 21.4462C5.20728 19.4838 9.61698 21.7515 11.8218 24.586C13.6298 28.6852 17.1135 36.8399 14.2031 38.061Z'
|
||||
fill='#FB006D'
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
BIN
frontend/appflowy_web_app/public/launch_splash.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
43
frontend/appflowy_web_app/scripts/create-symlink.cjs
Normal file
@ -0,0 +1,43 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const sourcePath = process.argv[2];
|
||||
const targetPath = process.argv[3];
|
||||
|
||||
// ensure source and target paths are provided
|
||||
if (!sourcePath || !targetPath) {
|
||||
console.error(chalk.red('source and target paths are required'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const fullSourcePath = path.resolve(sourcePath);
|
||||
const fullTargetPath = path.resolve(targetPath);
|
||||
// ensure source path exists
|
||||
if (!fs.existsSync(fullSourcePath)) {
|
||||
console.error(chalk.red(`source path does not exist: ${fullSourcePath}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// ensure target path exists
|
||||
if (!fs.existsSync(fullTargetPath)) {
|
||||
console.error(chalk.red(`target path does not exist: ${fullTargetPath}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
if (fs.existsSync(fullTargetPath)) {
|
||||
// unlink existing symlink
|
||||
console.log(chalk.yellow(`unlinking existing symlink: `) + chalk.blue(`${fullTargetPath}`));
|
||||
fs.unlinkSync(fullTargetPath);
|
||||
}
|
||||
|
||||
// create symlink
|
||||
fs.symlink(fullSourcePath, fullTargetPath, 'junction', (err) => {
|
||||
if (err) {
|
||||
console.error(chalk.red(`error creating symlink: ${err.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(chalk.green(`symlink created: `) + chalk.blue(`${fullSourcePath}`) + ' -> ' + chalk.blue(`${fullTargetPath}`));
|
||||
|
||||
});
|
63
frontend/appflowy_web_app/scripts/i18n.cjs
Normal file
@ -0,0 +1,63 @@
|
||||
const languages = [
|
||||
'ar-SA',
|
||||
'ca-ES',
|
||||
'de-DE',
|
||||
'en',
|
||||
'es-VE',
|
||||
'eu-ES',
|
||||
'fr-FR',
|
||||
'hu-HU',
|
||||
'id-ID',
|
||||
'it-IT',
|
||||
'ja-JP',
|
||||
'ko-KR',
|
||||
'pl-PL',
|
||||
'pt-BR',
|
||||
'pt-PT',
|
||||
'ru-RU',
|
||||
'sv-SE',
|
||||
'th-TH',
|
||||
'tr-TR',
|
||||
'zh-CN',
|
||||
'zh-TW',
|
||||
];
|
||||
|
||||
const fs = require('fs');
|
||||
languages.forEach(language => {
|
||||
const json = require(`../../resources/translations/${language}.json`);
|
||||
const outputJSON = flattenJSON(json);
|
||||
const output = JSON.stringify(outputJSON);
|
||||
const isExistDir = fs.existsSync('./src/@types/translations');
|
||||
if (!isExistDir) {
|
||||
fs.mkdirSync('./src/@types/translations');
|
||||
}
|
||||
fs.writeFile(`./src/@types/translations/${language}.json`, new Uint8Array(Buffer.from(output)), (res) => {
|
||||
if (res) {
|
||||
console.error(res);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
function flattenJSON(obj, prefix = '') {
|
||||
let result = {};
|
||||
const pluralsKey = ["one", "other", "few", "many", "two", "zero"];
|
||||
|
||||
for (let key in obj) {
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
|
||||
const nestedKeys = flattenJSON(obj[key], `${prefix}${key}.`);
|
||||
result = { ...result, ...nestedKeys };
|
||||
} else {
|
||||
let newKey = `${prefix}${key}`;
|
||||
let replaceChar = '{'
|
||||
if (pluralsKey.includes(key)) {
|
||||
newKey = `${prefix.slice(0, -1)}_${key}`;
|
||||
}
|
||||
result[newKey] = obj[key].replaceAll('{', '{{').replaceAll('}', '}}');
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
4
frontend/appflowy_web_app/src-tauri/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
.env
|
8085
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
Normal file
106
frontend/appflowy_web_app/src-tauri/Cargo.toml
Normal file
@ -0,0 +1,106 @@
|
||||
[package]
|
||||
name = "appflowy_tauri"
|
||||
version = "0.0.0"
|
||||
description = "A Tauri App"
|
||||
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.5", features = [] }
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0"
|
||||
tracing = "0.1.40"
|
||||
bytes = "1.5.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0.108"
|
||||
protobuf = { version = "2.28.0" }
|
||||
diesel = { version = "2.1.0", features = ["sqlite", "chrono", "r2d2"] }
|
||||
uuid = { version = "1.5.0", features = ["serde", "v4"] }
|
||||
serde_repr = "0.1"
|
||||
parking_lot = "0.12"
|
||||
futures = "0.3.29"
|
||||
tokio = "1.34.0"
|
||||
tokio-stream = "0.1.14"
|
||||
async-trait = "0.1.74"
|
||||
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
tauri = { version = "1.5", features = [
|
||||
"dialog-all",
|
||||
"clipboard-all",
|
||||
"fs-all",
|
||||
"shell-open",
|
||||
] }
|
||||
tauri-utils = "1.5.2"
|
||||
bytes.workspace = true
|
||||
tracing.workspace = true
|
||||
lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = [
|
||||
"use_serde",
|
||||
] }
|
||||
flowy-core = { path = "../../rust-lib/flowy-core", features = [
|
||||
"rev-sqlite",
|
||||
"ts",
|
||||
] }
|
||||
flowy-user = { path = "../../rust-lib/flowy-user", features = ["tauri_ts"] }
|
||||
flowy-config = { path = "../../rust-lib/flowy-config", features = ["tauri_ts"] }
|
||||
flowy-date = { path = "../../rust-lib/flowy-date", features = ["tauri_ts"] }
|
||||
flowy-error = { path = "../../rust-lib/flowy-error", features = [
|
||||
"impl_from_sqlite",
|
||||
"impl_from_dispatch_error",
|
||||
"impl_from_appflowy_cloud",
|
||||
"impl_from_reqwest",
|
||||
"impl_from_serde",
|
||||
"tauri_ts",
|
||||
] }
|
||||
flowy-document = { path = "../../rust-lib/flowy-document", features = [
|
||||
"tauri_ts",
|
||||
] }
|
||||
flowy-notification = { path = "../../rust-lib/flowy-notification", features = [
|
||||
"tauri_ts",
|
||||
] }
|
||||
|
||||
uuid = "1.5.0"
|
||||
tauri-plugin-deep-link = "0.1.2"
|
||||
dotenv = "0.15.0"
|
||||
|
||||
[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"]
|
||||
|
||||
[patch.crates-io]
|
||||
yrs = { git = "https://github.com/appflowy/y-crdt", rev = "3f25bb510ca5274e7657d3713fbed41fb46b4487" }
|
||||
|
||||
# Please using the following command to update the revision id
|
||||
# Current directory: frontend
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c77329ea11cc519b3158c7edf983b45b572dc360" }
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
# To update the commit ID, run:
|
||||
# scripts/tool/update_collab_rev.sh new_rev_id
|
||||
#
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a7a990dfc62a766829d28d2a9bb383840d8146f4" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a7a990dfc62a766829d28d2a9bb383840d8146f4" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a7a990dfc62a766829d28d2a9bb383840d8146f4" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a7a990dfc62a766829d28d2a9bb383840d8146f4" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a7a990dfc62a766829d28d2a9bb383840d8146f4" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a7a990dfc62a766829d28d2a9bb383840d8146f4" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a7a990dfc62a766829d28d2a9bb383840d8146f4" }
|
19
frontend/appflowy_web_app/src-tauri/Info.plist
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- Add this file next to your tauri.conf.json file -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<!-- Obviously needs to be replaced with your app's bundle identifier -->
|
||||
<string>appflowy-flutter</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>appflowy-flutter</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
3
frontend/appflowy_web_app/src-tauri/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
4
frontend/appflowy_web_app/src-tauri/env.development
Normal file
@ -0,0 +1,4 @@
|
||||
APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL=https://test.appflowy.cloud
|
||||
APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL=wss://test.appflowy.cloud/ws/v1
|
||||
APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL=https://test.appflowy.cloud/gotrue
|
||||
APPFLOWY_CLOUD_ENV_CLOUD_TYPE=2
|
4
frontend/appflowy_web_app/src-tauri/env.production
Normal file
@ -0,0 +1,4 @@
|
||||
APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL=https://beta.appflowy.cloud
|
||||
APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL=wss://beta.appflowy.cloud/ws/v1
|
||||
APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL=https://beta.appflowy.cloud/gotrue
|
||||
APPFLOWY_CLOUD_ENV_CLOUD_TYPE=2
|
BIN
frontend/appflowy_web_app/src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/icon.icns
Normal file
BIN
frontend/appflowy_web_app/src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
frontend/appflowy_web_app/src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 41 KiB |
2
frontend/appflowy_web_app/src-tauri/rust-toolchain.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.75"
|
12
frontend/appflowy_web_app/src-tauri/rustfmt.toml
Normal file
@ -0,0 +1,12 @@
|
||||
# https://rust-lang.github.io/rustfmt/?version=master&search=
|
||||
max_width = 100
|
||||
tab_spaces = 2
|
||||
newline_style = "Auto"
|
||||
match_block_trailing_comma = true
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
remove_nested_parens = true
|
||||
merge_derives = true
|
||||
edition = "2021"
|
59
frontend/appflowy_web_app/src-tauri/src/init.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use flowy_core::config::AppFlowyCoreConfig;
|
||||
use flowy_core::{AppFlowyCore, DEFAULT_NAME};
|
||||
use lib_dispatch::runtime::AFPluginRuntime;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dotenv::dotenv;
|
||||
|
||||
pub fn read_env() {
|
||||
dotenv().ok();
|
||||
|
||||
let env = if cfg!(debug_assertions) {
|
||||
include_str!("../env.development")
|
||||
} else {
|
||||
include_str!("../env.production")
|
||||
};
|
||||
|
||||
for line in env.lines() {
|
||||
if let Some((key, value)) = line.split_once('=') {
|
||||
// Check if the environment variable is not already set in the system
|
||||
let current_value = std::env::var(key).unwrap_or_default();
|
||||
if current_value.is_empty() {
|
||||
std::env::set_var(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_flowy_core() -> AppFlowyCore {
|
||||
let config_json = include_str!("../tauri.conf.json");
|
||||
let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
|
||||
|
||||
let app_version = config.package.version.clone().map(|v| v.to_string()).unwrap_or_else(|| "0.0.0".to_string());
|
||||
let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap();
|
||||
if cfg!(debug_assertions) {
|
||||
data_path.push("data_dev");
|
||||
} else {
|
||||
data_path.push("data");
|
||||
}
|
||||
|
||||
let custom_application_path = data_path.to_str().unwrap().to_string();
|
||||
let application_path = data_path.to_str().unwrap().to_string();
|
||||
let device_id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
read_env();
|
||||
std::env::set_var("RUST_LOG", "trace");
|
||||
|
||||
let config = AppFlowyCoreConfig::new(
|
||||
app_version,
|
||||
custom_application_path,
|
||||
application_path,
|
||||
device_id,
|
||||
DEFAULT_NAME.to_string(),
|
||||
)
|
||||
.log_filter("trace", vec!["appflowy_tauri".to_string()]);
|
||||
|
||||
let runtime = Arc::new(AFPluginRuntime::new().unwrap());
|
||||
let cloned_runtime = runtime.clone();
|
||||
runtime.block_on(async move { AppFlowyCore::new(config, cloned_runtime).await })
|
||||
}
|
71
frontend/appflowy_web_app/src-tauri/src/main.rs
Normal file
@ -0,0 +1,71 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const DEEP_LINK_SCHEME: &str = "appflowy-flutter";
|
||||
pub const OPEN_DEEP_LINK: &str = "open_deep_link";
|
||||
|
||||
mod init;
|
||||
mod notification;
|
||||
mod request;
|
||||
|
||||
use flowy_notification::{register_notification_sender, unregister_all_notification_sender};
|
||||
use init::*;
|
||||
use notification::*;
|
||||
use request::*;
|
||||
use tauri::Manager;
|
||||
extern crate dotenv;
|
||||
|
||||
fn main() {
|
||||
tauri_plugin_deep_link::prepare(DEEP_LINK_SCHEME);
|
||||
|
||||
let flowy_core = init_flowy_core();
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![invoke_request])
|
||||
.manage(flowy_core)
|
||||
.on_window_event(|_window_event| {})
|
||||
.on_menu_event(|_menu| {})
|
||||
.on_page_load(|window, _payload| {
|
||||
let app_handler = window.app_handle();
|
||||
// Make sure hot reload won't register the notification sender twice
|
||||
unregister_all_notification_sender();
|
||||
register_notification_sender(TSNotificationSender::new(app_handler.clone()));
|
||||
// tauri::async_runtime::spawn(async move {});
|
||||
|
||||
window.listen_global(AF_EVENT, move |event| {
|
||||
on_event(app_handler.clone(), event);
|
||||
});
|
||||
})
|
||||
.setup(|_app| {
|
||||
let splashscreen_window = _app.get_window("splashscreen").unwrap();
|
||||
let window = _app.get_window("main").unwrap();
|
||||
let handle = _app.handle();
|
||||
|
||||
// we perform the initialization code on a new task so the app doesn't freeze
|
||||
tauri::async_runtime::spawn(async move {
|
||||
// initialize your app here instead of sleeping :)
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
// After it's done, close the splashscreen and display the main window
|
||||
splashscreen_window.close().unwrap();
|
||||
window.show().unwrap();
|
||||
// If you need macOS support this must be called in .setup() !
|
||||
// Otherwise this could be called right after prepare() but then you don't have access to tauri APIs
|
||||
// On macOS You still have to install a .app bundle you got from tauri build --debug for this to work!
|
||||
tauri_plugin_deep_link::register(
|
||||
DEEP_LINK_SCHEME,
|
||||
move |request| {
|
||||
dbg!(&request);
|
||||
handle.emit_all(OPEN_DEEP_LINK, request).unwrap();
|
||||
},
|
||||
)
|
||||
.unwrap(/* If listening to the scheme is optional for your app, you don't want to unwrap here. */);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
35
frontend/appflowy_web_app/src-tauri/src/notification.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use flowy_notification::entities::SubscribeObject;
|
||||
use flowy_notification::NotificationSender;
|
||||
use serde::Serialize;
|
||||
use tauri::{AppHandle, Event, Manager, Wry};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const AF_EVENT: &str = "af-event";
|
||||
pub const AF_NOTIFICATION: &str = "af-notification";
|
||||
|
||||
#[tracing::instrument(level = "trace")]
|
||||
pub fn on_event(app_handler: AppHandle<Wry>, event: Event) {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn send_notification<P: Serialize + Clone>(app_handler: AppHandle<Wry>, payload: P) {
|
||||
app_handler.emit_all(AF_NOTIFICATION, payload).unwrap();
|
||||
}
|
||||
|
||||
pub struct TSNotificationSender {
|
||||
handler: AppHandle<Wry>,
|
||||
}
|
||||
|
||||
impl TSNotificationSender {
|
||||
pub fn new(handler: AppHandle<Wry>) -> Self {
|
||||
Self { handler }
|
||||
}
|
||||
}
|
||||
|
||||
impl NotificationSender for TSNotificationSender {
|
||||
fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> {
|
||||
self
|
||||
.handler
|
||||
.emit_all(AF_NOTIFICATION, subject)
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
}
|
45
frontend/appflowy_web_app/src-tauri/src/request.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use flowy_core::AppFlowyCore;
|
||||
use lib_dispatch::prelude::{
|
||||
AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode,
|
||||
};
|
||||
use tauri::{AppHandle, Manager, State, Wry};
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
pub struct AFTauriRequest {
|
||||
ty: String,
|
||||
payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::convert::From<AFTauriRequest> for AFPluginRequest {
|
||||
fn from(event: AFTauriRequest) -> Self {
|
||||
AFPluginRequest::new(event.ty).payload(event.payload)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
pub struct AFTauriResponse {
|
||||
code: StatusCode,
|
||||
payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::convert::From<AFPluginEventResponse> for AFTauriResponse {
|
||||
fn from(response: AFPluginEventResponse) -> Self {
|
||||
Self {
|
||||
code: response.status_code,
|
||||
payload: response.payload.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||
#[tauri::command]
|
||||
pub async fn invoke_request(
|
||||
request: AFTauriRequest,
|
||||
app_handler: AppHandle<Wry>,
|
||||
) -> AFTauriResponse {
|
||||
let request: AFPluginRequest = request.into();
|
||||
let state: State<AppFlowyCore> = app_handler.state();
|
||||
let dispatcher = state.inner().dispatcher();
|
||||
let response = AFPluginDispatcher::async_send(dispatcher.as_ref(), request).await;
|
||||
response.into()
|
||||
}
|
113
frontend/appflowy_web_app/src-tauri/tauri.conf.json
Normal file
@ -0,0 +1,113 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeBuildCommand": "npm run build:tauri",
|
||||
"beforeDevCommand": "npm run dev:tauri",
|
||||
"devPath": "http://localhost:5173",
|
||||
"distDir": "../dist",
|
||||
"withGlobalTauri": false
|
||||
},
|
||||
"package": {
|
||||
"productName": "AppFlowy",
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": true
|
||||
},
|
||||
"fs": {
|
||||
"all": true,
|
||||
"scope": [
|
||||
"$APPLOCALDATA/**"
|
||||
],
|
||||
"readFile": true,
|
||||
"writeFile": true,
|
||||
"readDir": true,
|
||||
"copyFile": true,
|
||||
"createDir": true,
|
||||
"removeDir": true,
|
||||
"removeFile": true,
|
||||
"renameFile": true,
|
||||
"exists": true
|
||||
},
|
||||
"clipboard": {
|
||||
"all": true,
|
||||
"writeText": true,
|
||||
"readText": true
|
||||
},
|
||||
"dialog": {
|
||||
"all": true,
|
||||
"ask": true,
|
||||
"confirm": true,
|
||||
"message": true,
|
||||
"open": true,
|
||||
"save": true
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"deb": {
|
||||
"depends": []
|
||||
},
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": [],
|
||||
"identifier": "com.appflowy.tauri",
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null,
|
||||
"minimumSystemVersion": "10.15.0"
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": ""
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"fileDropEnabled": false,
|
||||
"fullscreen": false,
|
||||
"height": 800,
|
||||
"resizable": true,
|
||||
"title": "AppFlowy",
|
||||
"width": 1200,
|
||||
"minWidth": 800,
|
||||
"minHeight": 600,
|
||||
"visible": false,
|
||||
"label": "main"
|
||||
},
|
||||
{
|
||||
"height": 300,
|
||||
"width": 549,
|
||||
"decorations": false,
|
||||
"url": "launch_splash.jpg",
|
||||
"label": "splashscreen",
|
||||
"center": true,
|
||||
"visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
8
frontend/appflowy_web_app/src/@types/i18next.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import resources from './resources';
|
||||
|
||||
declare module 'i18next' {
|
||||
interface CustomTypeOptions {
|
||||
defaultNS: 'translation';
|
||||
resources: typeof resources;
|
||||
}
|
||||
}
|
7
frontend/appflowy_web_app/src/@types/resources.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import translation from './translations/en.json';
|
||||
|
||||
const resources = {
|
||||
translation,
|
||||
} as const;
|
||||
|
||||
export default resources;
|
35
frontend/appflowy_web_app/src/App.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from './stores/store';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { ErrorHandlerPage } from './components/error/ErrorHandlerPage';
|
||||
import '@/i18n/config';
|
||||
import AppTheme from '@/AppTheme';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import ProtectedRoutes from '@/components/auth/ProtectedRoutes';
|
||||
import AppConfig from '@/AppConfig';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Provider store={store}>
|
||||
<AppTheme>
|
||||
<ErrorBoundary FallbackComponent={ErrorHandlerPage}>
|
||||
<AppConfig>
|
||||
<Routes>
|
||||
<Route path={'/'} element={<ProtectedRoutes />}>
|
||||
{/*<Route path={'/page/document/:id'} element={<DocumentPage />} />*/}
|
||||
{/*<Route path={'/page/grid/:id'} element={<DatabasePage />} />*/}
|
||||
{/*<Route path={'/trash'} id={'trash'} element={<TrashPage />} />*/}
|
||||
</Route>
|
||||
</Routes>
|
||||
<Toaster />
|
||||
</AppConfig>
|
||||
</ErrorBoundary>
|
||||
</AppTheme>
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
35
frontend/appflowy_web_app/src/AppConfig.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React, { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { AFService } from '@/application/services/services.type';
|
||||
import { getService } from '@/application/services';
|
||||
import { useAppSelector } from '@/stores/store';
|
||||
|
||||
export const AFConfigContext = createContext<
|
||||
| {
|
||||
service: AFService | undefined;
|
||||
}
|
||||
| undefined
|
||||
>(undefined);
|
||||
|
||||
|
||||
function AppConfig({ children }: { children: React.ReactNode }) {
|
||||
const appConfig = useAppSelector((state) => state.app.appConfig);
|
||||
const [service, setService] = useState<AFService>();
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
if (!appConfig) return;
|
||||
setService(await getService(appConfig));
|
||||
})();
|
||||
}, [appConfig]);
|
||||
|
||||
const config = useMemo(
|
||||
() => ({
|
||||
service,
|
||||
}),
|
||||
[service]
|
||||
);
|
||||
|
||||
return <AFConfigContext.Provider value={config}>{children}</AFConfigContext.Provider>;
|
||||
}
|
||||
|
||||
export default AppConfig;
|
177
frontend/appflowy_web_app/src/AppTheme.tsx
Normal file
@ -0,0 +1,177 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { createTheme, ThemeProvider } from '@mui/material';
|
||||
|
||||
function AppTheme({ children }: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const isDark = false;
|
||||
const theme = useMemo(() => createTheme({
|
||||
typography: {
|
||||
fontFamily: ['Poppins'].join(','),
|
||||
fontSize: 12,
|
||||
button: {
|
||||
textTransform: 'none',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiMenuItem: {
|
||||
defaultProps: {
|
||||
sx: {
|
||||
'&.Mui-selected.Mui-focusVisible': {
|
||||
backgroundColor: 'var(--fill-list-hover)',
|
||||
},
|
||||
'&.Mui-focusVisible': {
|
||||
backgroundColor: 'unset',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--fill-list-hover)',
|
||||
},
|
||||
borderRadius: '4px',
|
||||
padding: '2px',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
contained: {
|
||||
color: 'var(--content-on-fill)',
|
||||
boxShadow: 'var(--shadow)',
|
||||
},
|
||||
containedPrimary: {
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--fill-default)',
|
||||
},
|
||||
},
|
||||
containedInherit: {
|
||||
color: 'var(--text-title)',
|
||||
backgroundColor: isDark ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.4)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--bg-body)',
|
||||
boxShadow: 'var(--shadow)',
|
||||
},
|
||||
},
|
||||
outlinedInherit: {
|
||||
color: 'var(--text-title)',
|
||||
borderColor: 'var(--line-border)',
|
||||
'&:hover': {
|
||||
boxShadow: 'var(--shadow)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButtonBase: {
|
||||
defaultProps: {
|
||||
sx: {
|
||||
'&.Mui-selected:hover': {
|
||||
backgroundColor: 'var(--fill-list-hover)',
|
||||
},
|
||||
},
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--fill-list-hover)',
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: 'var(--fill-list-hover)',
|
||||
},
|
||||
borderRadius: '4px',
|
||||
padding: '2px',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiPaper: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundImage: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiDialog: {
|
||||
defaultProps: {
|
||||
sx: {
|
||||
'& .MuiBackdrop-root': {
|
||||
backgroundColor: 'var(--bg-mask)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
MuiTooltip: {
|
||||
styleOverrides: {
|
||||
arrow: {
|
||||
color: 'var(--bg-tips)',
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'var(--bg-tips)',
|
||||
color: 'var(--text-title)',
|
||||
fontSize: '0.85rem',
|
||||
borderRadius: '8px',
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiInputBase: {
|
||||
styleOverrides: {
|
||||
input: {
|
||||
backgroundColor: 'transparent !important',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiDivider: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderColor: 'var(--line-divider)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
palette: {
|
||||
mode: isDark ? 'dark' : 'light',
|
||||
primary: {
|
||||
main: '#00BCF0',
|
||||
dark: '#00BCF0',
|
||||
},
|
||||
error: {
|
||||
main: '#FB006D',
|
||||
dark: '#D32772',
|
||||
},
|
||||
warning: {
|
||||
main: '#FFC107',
|
||||
dark: '#E9B320',
|
||||
},
|
||||
info: {
|
||||
main: '#00BCF0',
|
||||
dark: '#2E9DBB',
|
||||
},
|
||||
success: {
|
||||
main: '#66CF80',
|
||||
dark: '#3BA856',
|
||||
},
|
||||
text: {
|
||||
primary: isDark ? '#E2E9F2' : '#333333',
|
||||
secondary: isDark ? '#7B8A9D' : '#828282',
|
||||
disabled: isDark ? '#363D49' : '#F2F2F2',
|
||||
},
|
||||
divider: isDark ? '#59647A' : '#BDBDBD',
|
||||
background: {
|
||||
default: isDark ? '#1A202C' : '#FFFFFF',
|
||||
paper: isDark ? '#1A202C' : '#FFFFFF',
|
||||
},
|
||||
},
|
||||
|
||||
}), [isDark]);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppTheme;
|
12
frontend/appflowy_web_app/src/application/services/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { AFService, AFServiceConfig } from '@/application/services/services.type';
|
||||
import { AFClientService } from '$client-services';
|
||||
|
||||
let service: AFService;
|
||||
|
||||
export async function getService(config: AFServiceConfig) {
|
||||
if (service) return service;
|
||||
|
||||
service = new AFClientService(config);
|
||||
await service.load();
|
||||
return service;
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import { AuthService } from '@/application/services/services.type';
|
||||
import { ProviderType, SignUpWithEmailPasswordParams, UserProfile } from '@/application/services/user.type';
|
||||
import { HttpClient } from '@/application/services/js-services/http/client';
|
||||
import { ACCESS_TOKEN_NAME, REFRESH_TOKEN_NAME, TOKEN_TYPE_NAME } from '@/application/services/js-services/http/const';
|
||||
import { AFWasmService } from '@/application/services/wasm-services';
|
||||
|
||||
|
||||
export class JSAuthService implements AuthService {
|
||||
|
||||
constructor (private httpClient: HttpClient, private wasmService: AFWasmService) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
getOAuthURL = async (_provider: ProviderType): Promise<string> => {
|
||||
return Promise.reject('Not implemented');
|
||||
};
|
||||
|
||||
signInWithOAuth = async ({ uri }: { uri: string }): Promise<UserProfile> => {
|
||||
const params = uri.split('#')[1].split('&');
|
||||
const data: Record<string, string> = {};
|
||||
|
||||
params.forEach((param) => {
|
||||
const [key, value] = param.split('=');
|
||||
|
||||
data[key] = value;
|
||||
});
|
||||
|
||||
sessionStorage.setItem(TOKEN_TYPE_NAME, data.token_type);
|
||||
sessionStorage.setItem(ACCESS_TOKEN_NAME, data.access_token);
|
||||
sessionStorage.setItem(REFRESH_TOKEN_NAME, data.refresh_token);
|
||||
return this.httpClient.getUser();
|
||||
};
|
||||
signupWithEmailPassword = async (_params: SignUpWithEmailPasswordParams): Promise<UserProfile> => {
|
||||
return Promise.reject('Not implemented');
|
||||
};
|
||||
|
||||
signinWithEmailPassword = async (email: string, password: string): Promise<UserProfile> => {
|
||||
await this.wasmService.cloudService.signIn(email, password);
|
||||
return Promise.reject('Not implemented');
|
||||
// return this.httpClient.signInWithEmailPassword(email, password);
|
||||
};
|
||||
|
||||
signOut = async (): Promise<void> => {
|
||||
return this.httpClient.logout();
|
||||
};
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { DocumentService } from '@/application/services/services.type';
|
||||
import { HttpClient } from '@/application/services/js-services/http/client';
|
||||
import { CollabType } from '@/application/services/js-services/http/http.type';
|
||||
|
||||
export class JSDocumentService implements DocumentService {
|
||||
constructor(private httpClient: HttpClient) {}
|
||||
|
||||
async openDocument(docID: string): Promise<void> {
|
||||
const workspaceId = '9eebea03-3ed5-4298-86b2-a7f77856d48b';
|
||||
const docId = '26d5c8c1-1c66-459c-bc6c-f4da1a663348';
|
||||
const data = await this.httpClient.getObject(workspaceId, docId, CollabType.Document);
|
||||
|
||||
console.log(docID, data);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
import { AxiosInstance } from 'axios';
|
||||
import { UserProfile, Workspace } from '@/application/services/user.type';
|
||||
import {
|
||||
CollabType,
|
||||
EncodedCollab,
|
||||
UserProfilePB,
|
||||
WorkspacePB,
|
||||
} from '@/application/services/js-services/http/http.type';
|
||||
import {
|
||||
parseUserPBToUserProfile,
|
||||
getAxiosInstances,
|
||||
parseWorkspacePBToWorkspace,
|
||||
} from '@/application/services/js-services/http/utils';
|
||||
import {
|
||||
ACCESS_TOKEN_NAME,
|
||||
baseHttpUrls,
|
||||
gotrueHttpUrls,
|
||||
REFRESH_TOKEN_NAME,
|
||||
URL_NAME,
|
||||
} from '@/application/services/js-services/http/const';
|
||||
|
||||
export class HttpClient {
|
||||
private gotrueAPI: AxiosInstance;
|
||||
private baseAPI: AxiosInstance;
|
||||
|
||||
constructor(private config: { baseURL: string; gotrueURL: string }) {
|
||||
const { baseInstance, gotrueInstance } = getAxiosInstances(config.baseURL, config.gotrueURL);
|
||||
|
||||
this.gotrueAPI = gotrueInstance;
|
||||
this.baseAPI = baseInstance;
|
||||
}
|
||||
|
||||
async signInWithEmailPassword(email: string, password: string): Promise<UserProfile> {
|
||||
const { data } = await this.gotrueAPI.post<{
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}>(gotrueHttpUrls[URL_NAME.SIGN_IN_WITH_EMAIL], {
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
sessionStorage.setItem(ACCESS_TOKEN_NAME, data.access_token);
|
||||
sessionStorage.setItem(REFRESH_TOKEN_NAME, data.refresh_token);
|
||||
|
||||
return this.getUser();
|
||||
}
|
||||
|
||||
async getUser(): Promise<UserProfile> {
|
||||
const { data } = await this.gotrueAPI.get<UserProfilePB>(gotrueHttpUrls[URL_NAME.GET_USER]);
|
||||
|
||||
return parseUserPBToUserProfile(data);
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this.gotrueAPI.post(gotrueHttpUrls[URL_NAME.LOGOUT]);
|
||||
sessionStorage.removeItem(REFRESH_TOKEN_NAME);
|
||||
sessionStorage.removeItem(ACCESS_TOKEN_NAME);
|
||||
}
|
||||
|
||||
async getWorkspaces(): Promise<Workspace[]> {
|
||||
const { data } = await this.baseAPI.get<WorkspacePB[]>(baseHttpUrls[URL_NAME.GET_WORKSPACES]);
|
||||
|
||||
return data.map(parseWorkspacePBToWorkspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object(document/database/view) from workspace
|
||||
* @param workspaceId - workspace id
|
||||
* @param objectId - document id or database id or view id
|
||||
* @param objectType - type of object [CollabType]
|
||||
*/
|
||||
async getObject(workspaceId: string, objectId: string, objectType: CollabType): Promise<EncodedCollab> {
|
||||
// const workspaces = await this.getWorkspaces();
|
||||
//
|
||||
// console.log(workspaces);
|
||||
const { data } = await this.baseAPI.get<EncodedCollab>(baseHttpUrls[URL_NAME.GET_OBJECT](workspaceId, objectId), {
|
||||
data: JSON.stringify({
|
||||
workspace_id: workspaceId,
|
||||
object_id: objectId,
|
||||
collab_type: objectType,
|
||||
}),
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
export enum URL_NAME {
|
||||
SIGN_IN_WITH_EMAIL,
|
||||
GET_USER,
|
||||
LOGOUT,
|
||||
REFRESH_TOKEN,
|
||||
GET_WORKSPACES,
|
||||
GET_OBJECT,
|
||||
}
|
||||
|
||||
export const gotrueHttpUrls = {
|
||||
[URL_NAME.SIGN_IN_WITH_EMAIL]: '/token?grant_type=password',
|
||||
[URL_NAME.GET_USER]: '/user',
|
||||
[URL_NAME.LOGOUT]: '/logout',
|
||||
[URL_NAME.REFRESH_TOKEN]: '/token?grant_type=refresh_token',
|
||||
};
|
||||
|
||||
export const baseHttpUrls = {
|
||||
[URL_NAME.GET_WORKSPACES]: '/api/workspace',
|
||||
[URL_NAME.GET_OBJECT]: (workspaceId: string, objectId: string) => `/api/workspace/${workspaceId}/collab/${objectId}`,
|
||||
};
|
||||
|
||||
export const ACCESS_TOKEN_NAME = 'access_token';
|
||||
export const REFRESH_TOKEN_NAME = 'refresh_token';
|
||||
export const TOKEN_TYPE_NAME = 'token_type';
|
||||
|
||||
export const AUTHORIZATION_NAME = 'Authorization';
|
@ -0,0 +1,40 @@
|
||||
export interface UserProfilePB {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
user_metadata: {
|
||||
avatar_url: string;
|
||||
full_name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WorkspacePB {
|
||||
workspace_id: string;
|
||||
database_storage_id: string;
|
||||
owner_uid: number;
|
||||
owner_name: string;
|
||||
workspace_type: number;
|
||||
workspace_name: string;
|
||||
created_at: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export enum EncoderVersion {
|
||||
V1 = 0,
|
||||
V2 = 1,
|
||||
}
|
||||
|
||||
export enum CollabType {
|
||||
Document = 0,
|
||||
Database = 1,
|
||||
WorkspaceDatabase = 2,
|
||||
Folder = 3,
|
||||
DatabaseRow = 4,
|
||||
UserAwareness = 5,
|
||||
}
|
||||
|
||||
export interface EncodedCollab {
|
||||
state_vector: Uint8Array;
|
||||
doc_state: Uint8Array;
|
||||
version: EncoderVersion;
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
import { UserProfilePB, WorkspacePB } from '@/application/services/js-services/http/http.type';
|
||||
import { Authenticator, UserProfile, Workspace } from '@/application/services/user.type';
|
||||
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosRequestConfig } from 'axios';
|
||||
import {
|
||||
ACCESS_TOKEN_NAME,
|
||||
AUTHORIZATION_NAME,
|
||||
gotrueHttpUrls,
|
||||
REFRESH_TOKEN_NAME,
|
||||
TOKEN_TYPE_NAME,
|
||||
URL_NAME,
|
||||
} from '@/application/services/js-services/http/const';
|
||||
|
||||
async function refreshToken(instance: AxiosInstance) {
|
||||
const refreshToken = sessionStorage.getItem(REFRESH_TOKEN_NAME);
|
||||
|
||||
if (!refreshToken) {
|
||||
throw new Error('Refresh token not found');
|
||||
}
|
||||
|
||||
const { data } = await instance.post(gotrueHttpUrls[URL_NAME.REFRESH_TOKEN], {
|
||||
refresh_token: refreshToken,
|
||||
});
|
||||
|
||||
sessionStorage.setItem(ACCESS_TOKEN_NAME, data.access_token);
|
||||
sessionStorage.setItem(REFRESH_TOKEN_NAME, data.refresh_token);
|
||||
|
||||
return data.access_token;
|
||||
}
|
||||
|
||||
export function getAxiosInstances(baseURL: string, gotrueURL: string) {
|
||||
const gotrueInstance = axios.create({
|
||||
baseURL: gotrueURL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: '*/*',
|
||||
},
|
||||
});
|
||||
const baseInstance = axios.create({
|
||||
baseURL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: '*/*',
|
||||
},
|
||||
});
|
||||
|
||||
const requestInterceptor = async (config: InternalAxiosRequestConfig) => {
|
||||
const accessToken = sessionStorage.getItem(ACCESS_TOKEN_NAME);
|
||||
const tokenType = sessionStorage.getItem(TOKEN_TYPE_NAME) || 'Bearer';
|
||||
|
||||
if (accessToken) {
|
||||
config.headers[AUTHORIZATION_NAME] = `${tokenType} ${accessToken}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
const errorInterceptor = async (error: {
|
||||
response?: AxiosResponse;
|
||||
config: AxiosRequestConfig;
|
||||
}) => {
|
||||
if (error.response?.status === 401 && !error.config.url?.includes(gotrueHttpUrls[URL_NAME.LOGOUT])) {
|
||||
try {
|
||||
const tokenType = sessionStorage.getItem(TOKEN_TYPE_NAME) || 'Bearer';
|
||||
const accessToken = await refreshToken(gotrueInstance);
|
||||
|
||||
const config = {
|
||||
...error.config,
|
||||
[AUTHORIZATION_NAME]: `${tokenType} ${accessToken}`,
|
||||
}
|
||||
|
||||
return gotrueInstance.request(config);
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
};
|
||||
|
||||
gotrueInstance.interceptors.request.use(requestInterceptor);
|
||||
gotrueInstance.interceptors.response.use((response) => response, errorInterceptor);
|
||||
|
||||
baseInstance.interceptors.request.use(requestInterceptor);
|
||||
baseInstance.interceptors.response.use((response) => response, errorInterceptor);
|
||||
return {
|
||||
baseInstance,
|
||||
gotrueInstance,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseUserPBToUserProfile(userPB: UserProfilePB): UserProfile {
|
||||
return {
|
||||
id: userPB.id,
|
||||
email: userPB.email,
|
||||
authenticator: Authenticator.AppFlowyCloud,
|
||||
iconUrl: userPB.user_metadata.avatar_url,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseWorkspacePBToWorkspace(workspacePB: WorkspacePB): Workspace {
|
||||
return {
|
||||
id: workspacePB.workspace_id,
|
||||
name: workspacePB.workspace_name,
|
||||
icon: workspacePB.icon,
|
||||
owner: {
|
||||
id: workspacePB.owner_uid,
|
||||
name: workspacePB.owner_name,
|
||||
},
|
||||
};
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import {
|
||||
AFService,
|
||||
AFServiceConfig,
|
||||
AuthService,
|
||||
DocumentService,
|
||||
UserService,
|
||||
} from '@/application/services/services.type';
|
||||
import { JSUserService } from '@/application/services/js-services/user.service';
|
||||
import { JSAuthService } from '@/application/services/js-services/auth.service';
|
||||
import { AFWasmService } from '@/application/services/wasm-services';
|
||||
import { HttpClient } from '@/application/services/js-services/http/client';
|
||||
import { JSDocumentService } from '@/application/services/js-services/document.service';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export class AFClientService implements AFService {
|
||||
authService: AuthService;
|
||||
userService: UserService;
|
||||
wasmService: AFWasmService;
|
||||
httpClient: HttpClient;
|
||||
documentService: DocumentService;
|
||||
private deviceId: string = nanoid(8);
|
||||
private clientId: string = 'web';
|
||||
getDeviceID = (): string => {
|
||||
return this.deviceId;
|
||||
};
|
||||
|
||||
getClientID = (): string => {
|
||||
return this.clientId;
|
||||
};
|
||||
|
||||
constructor(private config: AFServiceConfig) {
|
||||
this.wasmService = new AFWasmService(config, {
|
||||
deviceId: this.deviceId,
|
||||
clientId: this.clientId,
|
||||
});
|
||||
this.httpClient = new HttpClient({
|
||||
baseURL: config.cloudConfig.baseURL,
|
||||
gotrueURL: config.cloudConfig.gotrueURL,
|
||||
});
|
||||
this.authService = new JSAuthService(this.httpClient, this.wasmService);
|
||||
this.userService = new JSUserService(this.httpClient);
|
||||
this.documentService = new JSDocumentService(this.httpClient);
|
||||
}
|
||||
|
||||
async load() {
|
||||
await this.wasmService.load();
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { UserService } from '@/application/services/services.type';
|
||||
import { UserProfile } from '@/application/services/user.type';
|
||||
import { HttpClient } from '@/application/services/js-services/http/client';
|
||||
|
||||
export class JSUserService implements UserService {
|
||||
constructor(private httpClient: HttpClient) {}
|
||||
|
||||
async getUserProfile(): Promise<UserProfile> {
|
||||
return this.httpClient.getUser();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import { ProviderType, SignUpWithEmailPasswordParams, UserProfile } from '@/application/services/user.type';
|
||||
|
||||
export interface AFService {
|
||||
getDeviceID: () => string;
|
||||
getClientID: () => string;
|
||||
authService: AuthService;
|
||||
userService: UserService;
|
||||
documentService: DocumentService;
|
||||
load: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface AFServiceConfig {
|
||||
cloudConfig: AFCloudConfig;
|
||||
}
|
||||
|
||||
export interface AFCloudConfig {
|
||||
baseURL: string;
|
||||
gotrueURL: string;
|
||||
wsURL: string;
|
||||
}
|
||||
|
||||
export interface AuthService {
|
||||
|
||||
getOAuthURL: (provider: ProviderType) => Promise<string>;
|
||||
signInWithOAuth: (params: { uri: string }) => Promise<UserProfile>;
|
||||
signupWithEmailPassword: (params: SignUpWithEmailPasswordParams) => Promise<UserProfile>;
|
||||
signinWithEmailPassword: (email: string, password: string) => Promise<UserProfile>;
|
||||
signOut: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface DocumentService {
|
||||
openDocument: (docID: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface UserService {
|
||||
getUserProfile: () => Promise<UserProfile | null>;
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
import { AFCloudConfig, AuthService } from '@/application/services/services.type';
|
||||
import {
|
||||
AuthenticatorPB,
|
||||
OauthProviderPB,
|
||||
OauthSignInPB,
|
||||
SignInPayloadPB,
|
||||
SignUpPayloadPB,
|
||||
UserEventGetOauthURLWithProvider,
|
||||
UserEventOauthSignIn,
|
||||
UserEventSignInWithEmailPassword,
|
||||
UserEventSignOut,
|
||||
UserEventSignUp,
|
||||
UserProfilePB,
|
||||
} from './backend/events/flowy-user';
|
||||
import { ProviderType, SignUpWithEmailPasswordParams, UserProfile } from '@/application/services/user.type';
|
||||
|
||||
export class TauriAuthService implements AuthService {
|
||||
|
||||
constructor (private cloudConfig: AFCloudConfig, private clientConfig: {
|
||||
deviceId: string;
|
||||
clientId: string;
|
||||
|
||||
}) {}
|
||||
|
||||
getDeviceID = (): string => {
|
||||
return this.clientConfig.deviceId;
|
||||
};
|
||||
getOAuthURL = async (provider: ProviderType): Promise<string> => {
|
||||
const providerDataRes = await UserEventGetOauthURLWithProvider(
|
||||
OauthProviderPB.fromObject({
|
||||
provider: provider as number,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!providerDataRes.ok) {
|
||||
throw new Error(providerDataRes.val.msg);
|
||||
}
|
||||
|
||||
const providerData = providerDataRes.val;
|
||||
|
||||
return providerData.oauth_url;
|
||||
};
|
||||
|
||||
signInWithOAuth = async ({ uri }: { uri: string }): Promise<UserProfile> => {
|
||||
const payload = OauthSignInPB.fromObject({
|
||||
authenticator: AuthenticatorPB.AppFlowyCloud,
|
||||
map: {
|
||||
sign_in_url: uri,
|
||||
device_id: this.getDeviceID(),
|
||||
},
|
||||
});
|
||||
|
||||
const res = await UserEventOauthSignIn(payload);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(res.val.msg);
|
||||
}
|
||||
|
||||
return parseUserProfileFrom(res.val);
|
||||
};
|
||||
signinWithEmailPassword = async (email: string, password: string): Promise<UserProfile> => {
|
||||
const payload = SignInPayloadPB.fromObject({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
const res = await UserEventSignInWithEmailPassword(payload);
|
||||
|
||||
if (!res.ok) {
|
||||
return Promise.reject(res.val.msg);
|
||||
}
|
||||
|
||||
return parseUserProfileFrom(res.val);
|
||||
};
|
||||
|
||||
signupWithEmailPassword = async (params: SignUpWithEmailPasswordParams): Promise<UserProfile> => {
|
||||
const payload = SignUpPayloadPB.fromObject({
|
||||
name: params.name,
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
device_id: this.getDeviceID(),
|
||||
});
|
||||
|
||||
const res = await UserEventSignUp(payload);
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(res.val.msg);
|
||||
return Promise.reject(res.val.msg);
|
||||
}
|
||||
|
||||
return parseUserProfileFrom(res.val);
|
||||
};
|
||||
|
||||
signOut = async () => {
|
||||
const res = await UserEventSignOut();
|
||||
|
||||
if (!res.ok) {
|
||||
return Promise.reject(res.val.msg);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
export function parseUserProfileFrom (userPB: UserProfilePB): UserProfile {
|
||||
const user = userPB.toObject();
|
||||
|
||||
return {
|
||||
id: String(user.id),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
token: user.token,
|
||||
iconUrl: user.icon_url,
|
||||
openaiKey: user.openai_key,
|
||||
authenticator: user.authenticator as number,
|
||||
encryptionSign: user.encryption_sign,
|
||||
encryptionType: user.encryption_type as number,
|
||||
workspaceId: user.workspace_id,
|
||||
stabilityAiKey: user.stability_ai_key,
|
||||
};
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export * from "./models/flowy-user";
|
||||
export * from "./models/flowy-database2";
|
||||
export * from "./models/flowy-folder";
|
||||
export * from "./models/flowy-document";
|
||||
export * from "./models/flowy-error";
|
||||
export * from "./models/flowy-config";
|
||||
export * from "./models/flowy-date";
|
@ -0,0 +1,68 @@
|
||||
import { DocumentService } from '@/application/services/services.type';
|
||||
import { OpenDocumentPayloadPB } from './backend';
|
||||
import { DocumentEventOpenDocument } from './backend/events/flowy-document';
|
||||
|
||||
export class TauriDocumentService implements DocumentService {
|
||||
async openDocument(docId: string): Promise<void> {
|
||||
const payload = OpenDocumentPayloadPB.fromObject({
|
||||
document_id: docId,
|
||||
});
|
||||
|
||||
const result = await DocumentEventOpenDocument(payload);
|
||||
|
||||
if (!result.ok) {
|
||||
return Promise.reject(result.val);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
// const documentDataPB = result.val;
|
||||
//
|
||||
// if (!documentDataPB) {
|
||||
// return Promise.reject('documentDataPB is null');
|
||||
// }
|
||||
//
|
||||
// const data: {
|
||||
// viewId: string;
|
||||
// rootId: string;
|
||||
// nodeMap: Record<string, any>;
|
||||
// childrenMap: Record<string, string[]>;
|
||||
// relativeMap: Record<string, string>;
|
||||
// deltaMap: Record<string, Op[]>;
|
||||
// externalIdMap: Record<string, string>;
|
||||
// } = {
|
||||
// viewId: docId,
|
||||
// rootId: documentDataPB.page_id,
|
||||
// nodeMap: {},
|
||||
// childrenMap: {},
|
||||
// relativeMap: {},
|
||||
// deltaMap: {},
|
||||
// externalIdMap: {},
|
||||
// };
|
||||
//
|
||||
// get(documentDataPB, BLOCK_MAP_NAME).forEach((block) => {
|
||||
// Object.assign(data.nodeMap, {
|
||||
// [block.id]: blockPB2Node(block),
|
||||
// });
|
||||
// data.relativeMap[block.children_id] = block.id;
|
||||
// if (block.external_id) {
|
||||
// data.externalIdMap[block.external_id] = block.id;
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// get(documentDataPB, [META_NAME, CHILDREN_MAP_NAME]).forEach((child, key) => {
|
||||
// const blockId = data.relativeMap[key];
|
||||
//
|
||||
// data.childrenMap[blockId] = child.children;
|
||||
// });
|
||||
//
|
||||
// get(documentDataPB, [META_NAME, TEXT_MAP_NAME]).forEach((delta, key) => {
|
||||
// const blockId = data.externalIdMap[key];
|
||||
//
|
||||
// data.deltaMap[blockId] = delta ? JSON.parse(delta) : [];
|
||||
// });
|
||||
//
|
||||
// // return data;
|
||||
// return;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import {
|
||||
AFService,
|
||||
AFServiceConfig,
|
||||
AuthService,
|
||||
DocumentService,
|
||||
UserService,
|
||||
} from '@/application/services/services.type';
|
||||
import { TauriAuthService } from '@/application/services/tauri-services/auth.service';
|
||||
import { TauriUserService } from '@/application/services/tauri-services/user.service';
|
||||
import { TauriDocumentService } from '@/application/services/tauri-services/document.service';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export class AFClientService implements AFService {
|
||||
authService: AuthService;
|
||||
userService: UserService;
|
||||
documentService: DocumentService;
|
||||
private deviceId: string = nanoid(8);
|
||||
private clientId: string = 'web';
|
||||
getDeviceID = (): string => {
|
||||
return this.deviceId;
|
||||
};
|
||||
|
||||
getClientID = (): string => {
|
||||
return this.clientId;
|
||||
};
|
||||
|
||||
constructor(config: AFServiceConfig) {
|
||||
this.authService = new TauriAuthService(config.cloudConfig, {
|
||||
deviceId: this.deviceId,
|
||||
clientId: this.clientId,
|
||||
});
|
||||
this.userService = new TauriUserService();
|
||||
this.documentService = new TauriDocumentService();
|
||||
}
|
||||
|
||||
async load() {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { UserService } from '@/application/services/services.type';
|
||||
import { UserProfile } from '@/application/services/user.type';
|
||||
import { UserEventGetUserProfile } from './backend/events/flowy-user';
|
||||
import { parseUserProfileFrom } from '@/application/services/tauri-services/auth.service';
|
||||
|
||||
export class TauriUserService implements UserService {
|
||||
async getUserProfile (): Promise<UserProfile | null> {
|
||||
const res = await UserEventGetUserProfile();
|
||||
|
||||
if (res.ok) {
|
||||
return parseUserProfileFrom(res.val);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
export enum Authenticator {
|
||||
Local = 0,
|
||||
Supabase = 1,
|
||||
AppFlowyCloud = 2,
|
||||
}
|
||||
|
||||
export enum EncryptionType {
|
||||
NoEncryption = 0,
|
||||
Symmetric = 1,
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
id?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
token?: string;
|
||||
iconUrl?: string;
|
||||
openaiKey?: string;
|
||||
authenticator?: Authenticator;
|
||||
encryptionSign?: string;
|
||||
encryptionType?: EncryptionType;
|
||||
workspaceId?: string;
|
||||
stabilityAiKey?: string;
|
||||
}
|
||||
|
||||
export interface Workspace {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
owner: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SignUpWithEmailPasswordParams {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export enum ProviderType {
|
||||
Apple = 0,
|
||||
Azure = 1,
|
||||
Bitbucket = 2,
|
||||
Discord = 3,
|
||||
Facebook = 4,
|
||||
Figma = 5,
|
||||
Github = 6,
|
||||
Gitlab = 7,
|
||||
Google = 8,
|
||||
Keycloak = 9,
|
||||
Kakao = 10,
|
||||
Linkedin = 11,
|
||||
Notion = 12,
|
||||
Spotify = 13,
|
||||
Slack = 14,
|
||||
Workos = 15,
|
||||
Twitch = 16,
|
||||
Twitter = 17,
|
||||
Email = 18,
|
||||
Phone = 19,
|
||||
Zoom = 20,
|
||||
}
|
||||
|
||||
export interface UserSetting {
|
||||
workspaceId: string;
|
||||
latestView?: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
hasLatestView: boolean;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import { CloudServiceConfig } from '@/application/services/wasm-services/cloud.type';
|
||||
import { ClientAPI } from '@appflowyinc/client-api-wasm';
|
||||
|
||||
export class CloudService {
|
||||
private client?: ClientAPI;
|
||||
|
||||
constructor (private config: CloudServiceConfig) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
async init () {
|
||||
this.client = ClientAPI.new({
|
||||
base_url: this.config.baseURL,
|
||||
ws_addr: this.config.wsURL,
|
||||
gotrue_url: this.config.gotrueURL,
|
||||
device_id: this.config.deviceId,
|
||||
client_id: this.config.clientId,
|
||||
configuration: {
|
||||
compression_quality: 8,
|
||||
compression_buffer_size: 10240,
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async signIn (email: string, password: string) {
|
||||
try {
|
||||
const res = await this.client?.sign_in_password(email, password);
|
||||
|
||||
console.log(res);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { AFCloudConfig } from '@/application/services/services.type';
|
||||
|
||||
export type CloudServiceEventPayload = Record<string, string>;
|
||||
export type CloudServiceConfig = AFCloudConfig & {
|
||||
deviceId: string;
|
||||
clientId: string;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import { AFServiceConfig } from '@/application/services/services.type';
|
||||
import { CloudService } from '@/application/services/wasm-services/cloud.service';
|
||||
|
||||
export class AFWasmService {
|
||||
cloudService: CloudService;
|
||||
|
||||
constructor (private config: AFServiceConfig, clientConfig: {
|
||||
deviceId: string;
|
||||
clientId: string;
|
||||
}) {
|
||||
this.cloudService = new CloudService({
|
||||
...config.cloudConfig,
|
||||
...clientConfig,
|
||||
});
|
||||
}
|
||||
|
||||
async load () {
|
||||
await this.cloudService.init();
|
||||
}
|
||||
}
|
3
frontend/appflowy_web_app/src/assets/add.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 4C7.72386 4 7.5 4.22386 7.5 4.5V7.5H4.5C4.22386 7.5 4 7.72386 4 8C4 8.27614 4.22386 8.5 4.5 8.5H7.5V11.5C7.5 11.7761 7.72386 12 8 12C8.27614 12 8.5 11.7761 8.5 11.5V8.5H11.5C11.7761 8.5 12 8.27614 12 8C12 7.72386 11.7761 7.5 11.5 7.5H8.5V4.5C8.5 4.22386 8.27614 4 8 4Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 402 B |
5
frontend/appflowy_web_app/src/assets/align-center.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4L12 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 8H11" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 12L12 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 361 B |
5
frontend/appflowy_web_app/src/assets/align-left.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4L12 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 8H10" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 12L12 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 361 B |
5
frontend/appflowy_web_app/src/assets/align-right.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 4L12 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 8H12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 12L12 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 361 B |
3
frontend/appflowy_web_app/src/assets/arrow-left.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 4L6 8L10 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 195 B |
3
frontend/appflowy_web_app/src/assets/arrow-right.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4L10 8L6 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 194 B |
16
frontend/appflowy_web_app/src/assets/board.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M12.8 2H3.2C2.53726 2 2 2.55964 2 3.25V5.75C2 6.44036 2.53726 7 3.2 7H12.8C13.4627 7 14 6.44036 14 5.75V3.25C14 2.55964 13.4627 2 12.8 2Z'
|
||||
stroke='currentColor'
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
/>
|
||||
<path
|
||||
d='M12.8 9H3.2C2.53726 9 2 9.55964 2 10.25V12.75C2 13.4404 2.53726 14 3.2 14H12.8C13.4627 14 14 13.4404 14 12.75V10.25C14 9.55964 13.4627 9 12.8 9Z'
|
||||
stroke='currentColor'
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
/>
|
||||
<circle cx='4.5' cy='4.5' r='0.5' fill='currentColor' />
|
||||
<circle cx='4.5' cy='11.5' r='0.5' fill='currentColor' />
|
||||
</svg>
|
After Width: | Height: | Size: 788 B |
3
frontend/appflowy_web_app/src/assets/bold.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 8C9.66667 8 11 8.4 11 10C11 11.6 9.66667 12 9 12H6V8M9 8H6M9 8C9.5 8 10.5171 6.97616 10.5 6C10.4806 4.8956 9.5 4 8.5 4H6V8" stroke="#333333" stroke-width="1.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 277 B |
4
frontend/appflowy_web_app/src/assets/close.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="15.7124" y="7.22656" width="1.5" height="12" rx="0.75" transform="rotate(45 15.7124 7.22656)" fill="#333333"/>
|
||||
<rect x="16.7729" y="15.7109" width="1.5" height="12" rx="0.75" transform="rotate(135 16.7729 15.7109)" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 344 B |
4
frontend/appflowy_web_app/src/assets/copy.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.9743 6.33203H7.35889C6.79245 6.33203 6.33325 6.79123 6.33325 7.35767V11.9731C6.33325 12.5395 6.79245 12.9987 7.35889 12.9987H11.9743C12.5407 12.9987 12.9999 12.5395 12.9999 11.9731V7.35767C12.9999 6.79123 12.5407 6.33203 11.9743 6.33203Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.53846 9.66667H4.02564C3.75362 9.66667 3.49275 9.55861 3.3004 9.36626C3.10806 9.17392 3 8.91304 3 8.64103V4.02564C3 3.75362 3.10806 3.49275 3.3004 3.3004C3.49275 3.10806 3.75362 3 4.02564 3H8.64103C8.91304 3 9.17392 3.10806 9.36626 3.3004C9.55861 3.49275 9.66667 3.75362 9.66667 4.02564V4.53846" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 794 B |
73
frontend/appflowy_web_app/src/assets/dark-logo.svg
Normal file
@ -0,0 +1,73 @@
|
||||
<svg width='103' height='24' viewBox='0 0 103 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<g clip-path='url(#clip0_175_6957)'>
|
||||
<path
|
||||
d='M34.7324 7.84144H36.892V8.63601C37.4179 8.08166 38.4132 7.63818 39.5212 7.63818C41.8498 7.63818 43.3709 9.4121 43.3709 11.8882C43.3709 14.4197 41.6057 16.3969 38.8827 16.3969C38.1315 16.3969 37.3991 16.2675 36.9108 15.9904V19.9078H34.7512V7.84144H34.7324ZM36.9108 10.5393V13.9208C37.493 14.2904 37.9812 14.4012 38.6949 14.4012C40.1972 14.4012 41.0611 13.3295 41.0611 11.9436C41.0611 10.6132 40.2723 9.65231 38.8451 9.65231C38.1127 9.63384 37.4366 9.96644 36.9108 10.5393Z'
|
||||
fill='white'
|
||||
/>
|
||||
<path
|
||||
d='M29.3802 7.6748C26.6572 7.6748 24.892 9.6335 24.892 12.1835C24.892 14.6596 26.3943 16.4335 28.7417 16.4335C29.8309 16.4335 30.8262 15.99 31.3708 15.4357V16.2302H33.5305V9.22698C31.9154 8.02589 30.1314 7.6748 29.3802 7.6748ZM31.3521 13.5324C30.8262 14.1052 30.1502 14.4378 29.4178 14.4378C27.9906 14.4378 27.2018 13.477 27.2018 12.1465C27.2018 10.7607 28.0657 9.67046 29.568 9.67046C30.2816 9.67046 30.7699 9.78133 31.3521 10.1509V13.5324Z'
|
||||
fill='white'
|
||||
/>
|
||||
<path
|
||||
d='M44.291 7.84144H46.4506V8.63601C46.9765 8.08166 47.9718 7.63818 49.0797 7.63818C51.4084 7.63818 52.9295 9.4121 52.9295 11.8882C52.9295 14.4197 51.1642 16.3969 48.4412 16.3969C47.6901 16.3969 46.9577 16.2675 46.4694 15.9904V19.9078H44.3098V7.84144H44.291ZM46.4694 10.5393V13.9208C47.0516 14.2904 47.5398 14.4012 48.2535 14.4012C49.7558 14.4012 50.6196 13.3295 50.6196 11.9436C50.6196 10.6132 49.8309 9.65231 48.4037 9.65231C47.6713 9.63384 46.9952 9.96644 46.4694 10.5393Z'
|
||||
fill='white'
|
||||
/>
|
||||
<path
|
||||
d='M59.2019 5.43925C58.8826 5.21751 58.3756 4.99577 57.8685 4.99577C56.892 4.99577 56.3849 5.49469 56.3849 6.64034V7.82295H57.9061V9.83708H56.3849V16.1751H54.2065V6.49251C54.2065 4.18273 55.3333 3.00012 57.23 3.00012C58.0939 3.00012 58.8826 3.20338 59.446 3.55447H61.3615V13.0153C61.3615 13.9577 61.6056 14.3643 62.0939 14.3643C62.4507 14.3643 62.7324 14.2164 62.9953 14.0316L63.446 15.6577C62.9577 16.0458 62.2253 16.3599 61.2112 16.3599C59.9155 16.3599 59.2019 15.5838 59.2019 13.8838V5.43925Z'
|
||||
fill='white'
|
||||
/>
|
||||
<path
|
||||
d='M68.0846 7.63818C70.9015 7.63818 72.7043 9.50449 72.7043 12.0175C72.7043 14.5121 70.9015 16.3969 68.0846 16.3969C65.2677 16.3969 63.4648 14.5306 63.4648 12.0175C63.4648 9.50449 65.2864 7.63818 68.0846 7.63818ZM68.0846 14.4012C69.4179 14.4012 70.3944 13.4588 70.3944 12.0175C70.3944 10.6132 69.3991 9.65231 68.0846 9.65231C66.8076 9.65231 65.7935 10.5762 65.7935 12.0175C65.7935 13.4034 66.77 14.4012 68.0846 14.4012Z'
|
||||
fill='white'
|
||||
/>
|
||||
<path
|
||||
d='M83.4274 16.2308H81.4931L79.6903 11.5928C79.5588 11.2786 79.5025 10.9275 79.4086 10.6134C79.3335 10.983 79.2396 11.2971 79.1081 11.6297L77.3053 16.2308H75.4649L72.7043 7.84167H75.0142L76.3663 12.3873C76.4978 12.7938 76.5353 13.108 76.6105 13.496C76.7043 13.1449 76.7607 12.8493 76.9297 12.3873L78.5072 7.84167H80.4227L82.0565 12.3688C82.1879 12.7384 82.2818 13.1265 82.3757 13.5145C82.4508 13.1265 82.5447 12.7199 82.6386 12.3134L83.8781 7.84167H86.0565L83.4274 16.2308Z'
|
||||
fill='white'
|
||||
/>
|
||||
<path
|
||||
d='M90.6573 16.1936C89.4178 19.187 88.6291 19.9077 87.3709 19.9077C86.6009 19.9077 86.0375 19.612 85.5493 19.2609L86.2441 17.6533C86.4695 17.7827 86.7887 17.949 87.1267 17.949C87.6713 17.949 88.0657 17.524 88.4225 16.6555L88.6103 16.212L84.6854 7.85986H87.2958L89.2676 12.424C89.4366 12.8305 89.5493 13.2186 89.6807 13.6251C89.7558 13.237 89.831 12.8305 89.9812 12.4055L91.5023 7.85986H93.9624L90.6573 16.1936Z'
|
||||
fill='white'
|
||||
/>
|
||||
<path
|
||||
d='M19.0141 13.4033C18.5258 16.0088 16.385 18.2816 13.9249 19.5566C13.6244 19.7229 13.2676 19.8153 12.9296 19.8338H18.1502C18.6573 19.8338 19.0141 19.4642 19.0141 19.0022V13.4033Z'
|
||||
fill='#F7931E'
|
||||
/>
|
||||
<path
|
||||
d='M8.57282 8.37731C8.49771 8.43274 8.42259 8.48817 8.34747 8.54361C7.07048 9.43057 3.20193 12.2762 2.3193 11.0382C1.45545 9.83709 2.37564 6.43709 4.5916 4.79252C4.62916 4.75557 4.6855 4.73709 4.72306 4.70013C7.14559 3.01861 8.94841 3.25883 9.83104 4.47839C10.6573 5.62404 9.73714 7.43491 8.57282 8.37731Z'
|
||||
fill='#8427E0'
|
||||
/>
|
||||
<path
|
||||
d='M18.0376 11.0204C16.8357 11.852 14.9578 10.8911 14.0188 9.69C13.9812 9.63456 13.9437 9.59761 13.9061 9.54217C13.0047 8.28565 10.1127 4.47913 11.3521 3.62913C12.5916 2.76065 16.1784 3.70304 17.8122 5.99434C17.8498 6.04978 17.8873 6.08674 17.9249 6.14217C19.5024 8.45195 19.2582 10.1704 18.0376 11.0204Z'
|
||||
fill='#00B5FF'
|
||||
/>
|
||||
<path
|
||||
d='M16.4226 18.5219C16.385 18.5589 16.3475 18.5773 16.2911 18.6143C13.8686 20.2958 12.0658 20.0556 11.1832 18.836C10.3569 17.6904 11.277 15.8795 12.4414 14.9371C12.5165 14.8817 12.5916 14.8263 12.6667 14.7708C13.9437 13.9023 17.8123 11.0382 18.6761 12.2763C19.5587 13.4773 18.6573 16.8773 16.4226 18.5219Z'
|
||||
fill='#FFBD00'
|
||||
/>
|
||||
<path
|
||||
d='M9.66194 19.6861C8.4225 20.5545 4.85443 19.6121 3.22063 17.3208C3.18307 17.2839 3.14551 17.2285 3.12673 17.1915C1.53049 14.8817 1.7934 13.1448 3.01405 12.3132C4.21593 11.4817 6.09387 12.4426 7.03283 13.6437C7.07039 13.6991 7.10795 13.7361 7.14551 13.7915C8.02814 15.0295 10.9202 18.8361 9.66194 19.6861Z'
|
||||
fill='#E3006D'
|
||||
/>
|
||||
<path
|
||||
d='M8.57283 8.37731C6.84513 9.13491 3.16438 10.6871 2.61978 9.43057C2.1503 8.37731 3.01415 6.23383 4.59161 4.79252C4.62917 4.75557 4.68551 4.73709 4.72307 4.70013C7.1456 3.01861 8.94842 3.25883 9.83105 4.47839C10.6573 5.62404 9.73715 7.43491 8.57283 8.37731Z'
|
||||
fill='#9327FF'
|
||||
/>
|
||||
<path
|
||||
d='M18.0375 11.0196C16.8357 11.8511 14.9577 10.8902 14.0188 9.68912C13.2488 7.93368 11.7652 4.47825 13.0047 3.96086C14.1314 3.48042 16.4601 4.44129 17.9436 6.14129C19.5023 8.45107 19.2582 10.1696 18.0375 11.0196Z'
|
||||
fill='#00C8FF'
|
||||
/>
|
||||
<path
|
||||
d='M16.4226 18.5218C16.385 18.5587 16.3475 18.5772 16.2911 18.6142C13.8686 20.2957 12.0658 20.0555 11.1832 18.8359C10.3569 17.6903 11.277 15.8794 12.4414 14.937C14.1691 14.1794 17.8498 12.6272 18.3944 13.8837C18.8827 14.937 18.0188 17.0805 16.4226 18.5218Z'
|
||||
fill='#F7CF46'
|
||||
/>
|
||||
<path
|
||||
d='M8.04692 19.3535C6.92016 19.8339 4.59152 18.8915 3.12673 17.1915C1.53049 14.8817 1.7934 13.1448 3.01405 12.3132C4.21593 11.4817 6.09387 12.4426 7.03283 13.6437C7.80279 15.3806 9.28635 18.8361 8.04692 19.3535Z'
|
||||
fill='#FB006D'
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id='clip0_175_6957'>
|
||||
<rect width='92' height='17' fill='white' transform='translate(2 3)' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 6.8 KiB |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2" y="2" width="12" height="12" rx="4" fill="currentColor"/>
|
||||
<path d="M6 8L7.61538 9.5L10.5 6.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 273 B |
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="11" height="11" rx="3.5" stroke="#BDBDBD"/>
|
||||
</svg>
|
After Width: | Height: | Size: 176 B |
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 7.688L8.27223 12.1469C7.69304 12.6931 6.90749 13 6.0884 13C5.26931 13 4.48376 12.6931 3.90457 12.1469C3.32538 11.6006 3 10.8598 3 10.0873C3 9.31474 3.32538 8.57387 3.90457 8.02763L8.63234 3.56875C9.01847 3.20459 9.54216 3 10.0882 3C10.6343 3 11.158 3.20459 11.5441 3.56875C11.9302 3.93291 12.1472 4.42683 12.1472 4.94183C12.1472 5.45684 11.9302 5.95075 11.5441 6.31491L6.8112 10.7738C6.61814 10.9559 6.35629 11.0582 6.08326 11.0582C5.81022 11.0582 5.54838 10.9559 5.35531 10.7738C5.16225 10.5917 5.05379 10.3448 5.05379 10.0873C5.05379 9.82975 5.16225 9.58279 5.35531 9.40071L9.72297 5.28632" stroke="#333333" stroke-width="0.9989" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 797 B |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 8L8.11538 9.5L13.5 4.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 8.5V11.8889C13 12.1836 12.8829 12.4662 12.6746 12.6746C12.4662 12.8829 12.1836 13 11.8889 13H4.11111C3.81643 13 3.53381 12.8829 3.32544 12.6746C3.11706 12.4662 3 12.1836 3 11.8889V4.11111C3 3.81643 3.11706 3.53381 3.32544 3.32544C3.53381 3.11706 3.81643 3 4.11111 3H10.2222" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 561 B |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 8L8.11538 9.5L13.5 4.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5 8C13.5 11.0376 11.0376 13.5 8 13.5C4.96243 13.5 2.5 11.0376 2.5 8C2.5 4.96243 4.96243 2.5 8 2.5C8.81896 2.5 9.59612 2.679 10.2945 3" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 421 B |
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.8889 3.5H4.11111C3.49746 3.5 3 3.94772 3 4.5V11.5C3 12.0523 3.49746 12.5 4.11111 12.5H11.8889C12.5025 12.5 13 12.0523 13 11.5V4.5C13 3.94772 12.5025 3.5 11.8889 3.5Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 6.5H13" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 618 B |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 5V8L10 9" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 376 B |
@ -0,0 +1,8 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 4L12.5 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.5 8H12.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.5 12H12.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="4" cy="4" r="0.5" fill="#333333"/>
|
||||
<circle cx="4" cy="8" r="0.5" fill="#333333"/>
|
||||
<circle cx="4" cy="12" r="0.5" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 512 B |
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.201 6.4H3.001V12H2.081V7.384L0.953 7.704L0.729 6.92L2.201 6.4ZM3.91156 12V11.1L6.35156 8.61C6.9449 8.01667 7.24156 7.50333 7.24156 7.07C7.24156 6.73 7.13823 6.46667 6.93156 6.28C6.73156 6.08667 6.4749 5.99 6.16156 5.99C5.5749 5.99 5.14156 6.28 4.86156 6.86L3.89156 6.29C4.11156 5.82333 4.42156 5.47 4.82156 5.23C5.22156 4.99 5.6649 4.87 6.15156 4.87C6.7649 4.87 7.29156 5.06333 7.73156 5.45C8.17156 5.83667 8.39156 6.36333 8.39156 7.03C8.39156 7.74333 7.9949 8.50333 7.20156 9.31L5.62156 10.89H8.52156V12H3.91156ZM12.9025 7.032C13.5105 7.176 14.0025 7.46 14.3785 7.884C14.7625 8.3 14.9545 8.824 14.9545 9.456C14.9545 10.296 14.6705 10.956 14.1025 11.436C13.5345 11.916 12.8385 12.156 12.0145 12.156C11.3745 12.156 10.7985 12.008 10.2865 11.712C9.78253 11.416 9.41853 10.984 9.19453 10.416L10.3705 9.732C10.6185 10.452 11.1665 10.812 12.0145 10.812C12.4945 10.812 12.8745 10.692 13.1545 10.452C13.4345 10.204 13.5745 9.872 13.5745 9.456C13.5745 9.04 13.4345 8.712 13.1545 8.472C12.8745 8.232 12.4945 8.112 12.0145 8.112H11.7025L11.1505 7.284L12.9625 4.896H9.44653V3.6H14.6065V4.776L12.9025 7.032Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="8" cy="5" r="2.5" stroke="#333333"/>
|
||||
<path d="M3 13C3 10.2386 5.23858 8 8 8C10.7614 8 13 10.2386 13 13" stroke="#333333" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 261 B |
@ -0,0 +1,8 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="11.8274" cy="5.82739" r="1.5" stroke="#333333"/>
|
||||
<path d="M10.5008 5.38471L6.24097 4.78992" stroke="#333333"/>
|
||||
<path d="M4.86475 6.24121L6.02777 10.1009" stroke="#333333"/>
|
||||
<circle cx="7" cy="11" r="1.5" stroke="#333333"/>
|
||||
<circle cx="5" cy="5" r="1.5" stroke="#333333"/>
|
||||
<path d="M10.9011 7.14258L8.1484 10.0447" stroke="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 448 B |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.78787 8.78787L6.51213 7.51213C6.32314 7.32314 6.45699 7 6.72426 7H9.27574C9.54301 7 9.67686 7.32314 9.48787 7.51213L8.21213 8.78787C8.09497 8.90503 7.90503 8.90503 7.78787 8.78787Z" fill="#333333"/>
|
||||
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 499 B |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.15625 11.8359L6.43768 9.85414H2.46662L1.74805 11.8359H0.5L3.7903 3H5.11399L8.4043 11.8359H7.15625ZM2.87003 8.75596H6.03427L4.44584 4.40112L2.87003 8.75596Z" fill="#333333"/>
|
||||
<path d="M14.4032 5.52454H15.5V11.8359H14.4032V10.7504C13.8569 11.5835 13.0627 12 12.0206 12C11.1381 12 10.386 11.6802 9.76403 11.0407C9.14211 10.3927 8.83114 9.60589 8.83114 8.68022C8.83114 7.75456 9.14211 6.97195 9.76403 6.3324C10.386 5.68443 11.1381 5.36045 12.0206 5.36045C13.0627 5.36045 13.8569 5.777 14.4032 6.6101V5.52454ZM12.1593 10.9397C12.798 10.9397 13.3317 10.7251 13.7603 10.2959C14.1889 9.85835 14.4032 9.31978 14.4032 8.68022C14.4032 8.04067 14.1889 7.50631 13.7603 7.07714C13.3317 6.63955 12.798 6.42076 12.1593 6.42076C11.5289 6.42076 10.9995 6.63955 10.5708 7.07714C10.1422 7.50631 9.92791 8.04067 9.92791 8.68022C9.92791 9.31978 10.1422 9.85835 10.5708 10.2959C10.9995 10.7251 11.5289 10.9397 12.1593 10.9397Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 7.688L8.27223 12.1469C7.69304 12.6931 6.90749 13 6.0884 13C5.26931 13 4.48376 12.6931 3.90457 12.1469C3.32538 11.6006 3 10.8598 3 10.0873C3 9.31474 3.32538 8.57387 3.90457 8.02763L8.63234 3.56875C9.01847 3.20459 9.54216 3 10.0882 3C10.6343 3 11.158 3.20459 11.5441 3.56875C11.9302 3.93291 12.1472 4.42683 12.1472 4.94183C12.1472 5.45684 11.9302 5.95075 11.5441 6.31491L6.8112 10.7738C6.61814 10.9559 6.35629 11.0582 6.08326 11.0582C5.81022 11.0582 5.54838 10.9559 5.35531 10.7738C5.16225 10.5917 5.05379 10.3448 5.05379 10.0873C5.05379 9.82975 5.16225 9.58279 5.35531 9.40071L9.72297 5.28632" stroke="#333333" stroke-width="0.9989" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 797 B |
6
frontend/appflowy_web_app/src/assets/date.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.8889 3.5H4.11111C3.49746 3.5 3 3.94772 3 4.5V11.5C3 12.0523 3.49746 12.5 4.11111 12.5H11.8889C12.5025 12.5 13 12.0523 13 11.5V4.5C13 3.94772 12.5025 3.5 11.8889 3.5Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 6.5H13" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 618 B |
6
frontend/appflowy_web_app/src/assets/delete.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 4.40039H4.11111H13" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.77775 4.4V3.2C5.77775 2.88174 5.89481 2.57652 6.10319 2.35147C6.31156 2.12643 6.59418 2 6.88886 2H9.11108C9.40577 2 9.68838 2.12643 9.89676 2.35147C10.1051 2.57652 10.2222 2.88174 10.2222 3.2V4.4M11.8889 4.4V12.8C11.8889 13.1183 11.7718 13.4235 11.5634 13.6485C11.3551 13.8736 11.0724 14 10.7778 14H5.2222C4.92751 14 4.64489 13.8736 4.43652 13.6485C4.22815 13.4235 4.11108 13.1183 4.11108 12.8V4.4H11.8889Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.88892 7.40039V11.0004" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.11108 7.40039V11.0004" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 889 B |
5
frontend/appflowy_web_app/src/assets/details.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 8C5 8.55228 4.55228 9 4 9C3.44772 9 3 8.55228 3 8C3 7.44772 3.44772 7 4 7C4.55228 7 5 7.44772 5 8Z" fill="currentColor"/>
|
||||
<path d="M9 8C9 8.55228 8.55229 9 8 9C7.44772 9 7 8.55228 7 8C7 7.44772 7.44772 7 8 7C8.55229 7 9 7.44772 9 8Z" fill="currentColor"/>
|
||||
<path d="M12 9C12.5523 9 13 8.55228 13 8C13 7.44772 12.5523 7 12 7C11.4477 7 11 7.44772 11 8C11 8.55228 11.4477 9 12 9Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 514 B |