mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat: add basic tauri client app
This commit is contained in:
77
.github/workflows/client-app.yml
vendored
Normal file
77
.github/workflows/client-app.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
name: Release App
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
paths-ignore:
|
||||||
|
- 'README.md'
|
||||||
|
- 'docs/**'
|
||||||
|
- '.vscode'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'README.md'
|
||||||
|
- '.vscode'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-ci-group
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
MacOS:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18.x
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
- name: Get npm cache directory
|
||||||
|
uses: actions/cache@v3
|
||||||
|
id: cache
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm i -r
|
||||||
|
- name: Install OctoBase
|
||||||
|
run: pnpm build:prerequisite
|
||||||
|
working-directory: client-app
|
||||||
|
|
||||||
|
- name: Make macOS (x64)
|
||||||
|
run: pnpm build:app
|
||||||
|
working-directory: client-app
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
CI_PULL_REQUEST: ${{ github.event_name == 'pull_request' }}
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
generate_release_notes: true
|
||||||
|
files: client-app/src-tauri/target/release/bundle/dmg/*.dmg
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "client-app/src-OctoBase"]
|
||||||
|
path = client-app/src-OctoBase
|
||||||
|
url = https://github.com/toeverything/OctoBase
|
||||||
23
.vscode/settings.json
vendored
23
.vscode/settings.json
vendored
@@ -3,5 +3,26 @@
|
|||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.formatOnSaveMode": "file",
|
"editor.formatOnSaveMode": "file",
|
||||||
"cSpell.words": ["blocksuite", "datacenter", "livedemo", "pnpm", "testid"]
|
"cSpell.words": [
|
||||||
|
"blocksuite",
|
||||||
|
"datacenter",
|
||||||
|
"livedemo",
|
||||||
|
"pnpm",
|
||||||
|
"jwst",
|
||||||
|
"testid",
|
||||||
|
"octobase",
|
||||||
|
"schemars"
|
||||||
|
],
|
||||||
|
"explorer.fileNesting.enabled": true,
|
||||||
|
"explorer.fileNesting.expand": false,
|
||||||
|
"explorer.fileNesting.patterns": {
|
||||||
|
"*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map",
|
||||||
|
"package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, cypress.*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.config.*, webpack*, workspace.json, xo.config.*, yarn*"
|
||||||
|
},
|
||||||
|
"[rust]": {
|
||||||
|
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||||
|
},
|
||||||
|
"[toml]": {
|
||||||
|
"editor.defaultFormatter": "tamasfe.even-better-toml"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
client-app/.editorconfig
Normal file
29
client-app/.editorconfig
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.scss]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.jsx]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.tsx]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.vue]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
1
client-app/.gitattributes
vendored
Normal file
1
client-app/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto
|
||||||
30
client-app/.gitignore
vendored
Normal file
30
client-app/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-isolation
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# generated assets
|
||||||
|
public/affine-out
|
||||||
|
public/preload
|
||||||
6
client-app/.gitmodules
vendored
Normal file
6
client-app/.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[submodule "src-affine"]
|
||||||
|
path = src-affine
|
||||||
|
url = https://github.com/toeverything/AFFiNE
|
||||||
|
[submodule "src-OctoBase"]
|
||||||
|
path = src-OctoBase
|
||||||
|
url = https://github.com/toeverything/OctoBase
|
||||||
1
client-app/.nvmrc
Normal file
1
client-app/.nvmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
v18
|
||||||
384
client-app/LICENSE
Normal file
384
client-app/LICENSE
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
# Mozilla Public License Version 2.0
|
||||||
|
|
||||||
|
Copyright (c) Toeverything Technology (Hangzhou) Co., Ltd. and its affiliates.
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- *
|
||||||
|
- 6. Disclaimer of Warranty \*
|
||||||
|
- ------------------------- \*
|
||||||
|
- *
|
||||||
|
- Covered Software is provided under this License on an "as is" \*
|
||||||
|
- basis, without warranty of any kind, either expressed, implied, or \*
|
||||||
|
- statutory, including, without limitation, warranties that the \*
|
||||||
|
- Covered Software is free of defects, merchantable, fit for a \*
|
||||||
|
- particular purpose or non-infringing. The entire risk as to the \*
|
||||||
|
- quality and performance of the Covered Software is with You. \*
|
||||||
|
- Should any Covered Software prove defective in any respect, You \*
|
||||||
|
- (not any Contributor) assume the cost of any necessary servicing, \*
|
||||||
|
- repair, or correction. This disclaimer of warranty constitutes an \*
|
||||||
|
- essential part of this License. No use of any Covered Software is \*
|
||||||
|
- authorized under this License except under this disclaimer. \*
|
||||||
|
- *
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- *
|
||||||
|
- 7. Limitation of Liability \*
|
||||||
|
- -------------------------- \*
|
||||||
|
- *
|
||||||
|
- Under no circumstances and under no legal theory, whether tort \*
|
||||||
|
- (including negligence), contract, or otherwise, shall any \*
|
||||||
|
- Contributor, or anyone who distributes Covered Software as \*
|
||||||
|
- permitted above, be liable to You for any direct, indirect, \*
|
||||||
|
- special, incidental, or consequential damages of any character \*
|
||||||
|
- including, without limitation, damages for lost profits, loss of \*
|
||||||
|
- goodwill, work stoppage, computer failure or malfunction, or any \*
|
||||||
|
- and all other commercial damages or losses, even if such party \*
|
||||||
|
- shall have been informed of the possibility of such damages. This \*
|
||||||
|
- limitation of liability shall not apply to liability for death or \*
|
||||||
|
- personal injury resulting from such party's negligence to the \*
|
||||||
|
- extent applicable law prohibits such limitation. Some \*
|
||||||
|
- jurisdictions do not allow the exclusion or limitation of \*
|
||||||
|
- incidental or consequential damages, so this exclusion and \*
|
||||||
|
- limitation may not apply to You. \*
|
||||||
|
- *
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
## Exhibit A - Source Code Form License Notice
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
## Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
23
client-app/README.md
Normal file
23
client-app/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Client App
|
||||||
|
|
||||||
|
AFFiNE App client powered by Tauri.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Please follow the Tauri [getting started guide](https://tauri.app/v1/guides/getting-started/setup/) for environment setup.
|
||||||
|
|
||||||
|
After the environment is ready, start development build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm tauri dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Currently client-app depends on a rapidly developing rust library "Octobase", we use git-submodule to link it currently.
|
||||||
|
|
||||||
|
We will provide its binary binding soon, to replace the git-submodule, before Octobase become opensource.
|
||||||
|
|
||||||
|
### Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||||
13
client-app/index.html
Normal file
13
client-app/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="/src/style.css" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>AFFiNE</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="react-root" />
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
client-app/package.json
Normal file
69
client-app/package.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"name": "@affine/client-app",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"module": "true",
|
||||||
|
"scripts": {
|
||||||
|
"dev:app": "tauri dev",
|
||||||
|
"build:prerequisite": "pnpm build:submodules && pnpm build:preload",
|
||||||
|
"dev:web": "vite",
|
||||||
|
"build:rs-types": "zx scripts/generateTsTypingsFromJsonSchema.mjs",
|
||||||
|
"build:web": "tsc && vite build",
|
||||||
|
"build:submodules": "zx scripts/buildSubModules.mjs",
|
||||||
|
"build:preload": "esbuild src/preload/index.ts --outdir=public/preload",
|
||||||
|
"build:app": "tauri build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.10.5",
|
||||||
|
"@emotion/styled": "^11.10.5",
|
||||||
|
"@tauri-apps/api": "^1.2.0",
|
||||||
|
"json-schema-to-typescript": "^11.0.2",
|
||||||
|
"lib0": "^0.2.58",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-is": "^18.2.0",
|
||||||
|
"react-router": "^6.5.0",
|
||||||
|
"react-router-dom": "^6.5.0",
|
||||||
|
"y-protocols": "^1.0.5",
|
||||||
|
"yjs": "^13.5.43"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^1.2.2",
|
||||||
|
"@types/node": "^18.11.17",
|
||||||
|
"@types/react": "^18.0.26",
|
||||||
|
"@types/react-dom": "^18.0.9",
|
||||||
|
"@typescript-eslint/eslint-plugin": "5.47.0",
|
||||||
|
"@typescript-eslint/parser": "5.47.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"esbuild": "^0.16.10",
|
||||||
|
"eslint": "8.30.0",
|
||||||
|
"eslint-config-prettier": "8.5.0",
|
||||||
|
"eslint-config-standard": "^17.0.0",
|
||||||
|
"eslint-config-standard-with-typescript": "24.0.0",
|
||||||
|
"eslint-import-resolver-alias": "1.1.2",
|
||||||
|
"eslint-import-resolver-typescript": "3.5.2",
|
||||||
|
"eslint-plugin-autofix": "1.1.0",
|
||||||
|
"eslint-plugin-html": "7.1.0",
|
||||||
|
"eslint-plugin-import": "^2.26.0",
|
||||||
|
"eslint-plugin-n": "^15.6.0",
|
||||||
|
"eslint-plugin-node": "11.1.0",
|
||||||
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
|
"eslint-plugin-react": "7.31.11",
|
||||||
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
|
"eslint-plugin-security": "1.5.0",
|
||||||
|
"eslint-plugin-security-node": "1.1.1",
|
||||||
|
"eslint-plugin-typescript-sort-keys": "2.1.0",
|
||||||
|
"eslint-plugin-unicorn": "45.0.2",
|
||||||
|
"eslint-plugin-unused-imports": "2.0.0",
|
||||||
|
"prettier": "2.8.1",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"typescript": "^4.9.4",
|
||||||
|
"typesync": "^0.9.2",
|
||||||
|
"vite": "^4.0.2",
|
||||||
|
"zx": "^7.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
4041
client-app/pnpm-lock.yaml
generated
Normal file
4041
client-app/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
client-app/scripts/buildSubModules.mjs
Normal file
17
client-app/scripts/buildSubModules.mjs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
const repoDirectory = path.join(__dirname, '..');
|
||||||
|
const publicDistributionDirectory = path.join(repoDirectory, 'public');
|
||||||
|
|
||||||
|
const octoBaseBranchName = 'master';
|
||||||
|
/**
|
||||||
|
* 1. Until OctoBase become public, we link it using submodule too.
|
||||||
|
*/
|
||||||
|
cd(`${path.join(repoDirectory, 'src-OctoBase')}`);
|
||||||
|
await $`git checkout ${octoBaseBranchName}`;
|
||||||
|
await $`git submodule update --recursive && git submodule update --remote`;
|
||||||
|
await $`git pull origin ${octoBaseBranchName}`;
|
||||||
44
client-app/scripts/generateTsTypingsFromJsonSchema.mjs
Normal file
44
client-app/scripts/generateTsTypingsFromJsonSchema.mjs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
// TODO: use https://github.com/quicktype/quicktype#installation instead
|
||||||
|
import { compileFromFile } from 'json-schema-to-typescript';
|
||||||
|
import { cd } from 'zx/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. generate JSONSchema using rs crate `schemars`, this happened on rs side script `src-tauri/examples/generate-jsonschema.rs`
|
||||||
|
*/
|
||||||
|
cd('./src-tauri');
|
||||||
|
await $`cargo run --example generate-jsonschema`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. generate TS from JSON schema, this is efficient on NodeJS side.
|
||||||
|
*/
|
||||||
|
const tsTypingsFolder = path.join(__dirname, '..', 'src', 'types', 'ipc');
|
||||||
|
const fileNames = fs.readdirSync(tsTypingsFolder);
|
||||||
|
const jsonSchemaFilePaths = fileNames
|
||||||
|
.filter(fileName => fileName.endsWith('.json'))
|
||||||
|
.map(fileName => path.join(tsTypingsFolder, fileName));
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
jsonSchemaFilePaths.map(
|
||||||
|
async fileName =>
|
||||||
|
await compileFromFile(fileName).then(tsContent =>
|
||||||
|
fs.writeFileSync(fileName.replace('.json', '.ts'), tsContent)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3. fix eslint error on generated ts files
|
||||||
|
*/
|
||||||
|
await $`eslint ${tsTypingsFolder} --ext ts --fix`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4. // TODO: parse all #[tauri::command] and generate ts method code
|
||||||
|
*/
|
||||||
4
client-app/src-tauri/.gitignore
vendored
Normal file
4
client-app/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
4957
client-app/src-tauri/Cargo.lock
generated
Normal file
4957
client-app/src-tauri/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
client-app/src-tauri/Cargo.toml
Normal file
41
client-app/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
[package]
|
||||||
|
name = "AFFiNE"
|
||||||
|
version = "0.0.0"
|
||||||
|
description = "Development Tool for BlockSuite"
|
||||||
|
authors = ["you"]
|
||||||
|
license = ""
|
||||||
|
repository = ""
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.57"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = {version = "1.2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "1.3.0"
|
||||||
|
ipc_types = { path = "./types" }
|
||||||
|
futures = "^0.3.25"
|
||||||
|
js-sys = "0.3.60"
|
||||||
|
jwst = { path = "../src-OctoBase/libs/jwst" }
|
||||||
|
jwst-storage = { path = "../src-OctoBase/libs/jwst-storage", features = ["sqlite"] }
|
||||||
|
lib0 = "0.12.0"
|
||||||
|
project-root = "0.2.2"
|
||||||
|
schemars = "0.8.3"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
tauri = {version = "1.2", features = ["api-all", "devtools"] }
|
||||||
|
tokio = { version = "1.23.0", features = ["rt", "macros"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# by default Tauri runs in production mode
|
||||||
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
default = [ "custom-protocol" ]
|
||||||
|
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||||
|
# DO NOT remove this
|
||||||
|
custom-protocol = [ "tauri/custom-protocol" ]
|
||||||
|
|
||||||
|
[profile.release.package.wry]
|
||||||
|
debug = true
|
||||||
|
debug-assertions = true
|
||||||
3
client-app/src-tauri/build.rs
Normal file
3
client-app/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
30
client-app/src-tauri/examples/generate-jsonschema.rs
Normal file
30
client-app/src-tauri/examples/generate-jsonschema.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use ipc_types::{
|
||||||
|
blob::IBlobParameters, document::YDocumentUpdate, workspace::CreateWorkspace,
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* convert serde to jsonschema: https://imfeld.dev/writing/generating_typescript_types_from_rust
|
||||||
|
* with way to optimize
|
||||||
|
* convert jsonschema to ts: https://github.com/bcherny/json-schema-to-typescript
|
||||||
|
*/
|
||||||
|
use project_root::get_project_root;
|
||||||
|
use schemars::{schema_for, JsonSchema};
|
||||||
|
use std::{
|
||||||
|
fs::write,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn generate<T>(path: PathBuf)
|
||||||
|
where
|
||||||
|
T: ?Sized + JsonSchema, // Sized or ?Sized are both ok, click https://zhuanlan.zhihu.com/p/21820917 to learn why
|
||||||
|
{
|
||||||
|
let schema = schema_for!(T);
|
||||||
|
let output = serde_json::to_string_pretty(&schema).unwrap();
|
||||||
|
write(path, output).expect("can not write json-schema file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let project_root = &get_project_root().unwrap();
|
||||||
|
generate::<YDocumentUpdate>(Path::join(project_root, "../src/types/ipc/document.json"));
|
||||||
|
generate::<CreateWorkspace>(Path::join(project_root, "../src/types/ipc/workspace.json"));
|
||||||
|
generate::<IBlobParameters>(Path::join(project_root, "../src/types/ipc/blob.json"));
|
||||||
|
}
|
||||||
1
client-app/src-tauri/rustfmt.toml
Normal file
1
client-app/src-tauri/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tab_spaces = 2
|
||||||
12
client-app/src-tauri/src/commands.rs
Normal file
12
client-app/src-tauri/src/commands.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
pub mod blob;
|
||||||
|
pub mod workspace;
|
||||||
|
|
||||||
|
use workspace::{__cmd__create_workspace, __cmd__update_y_document};
|
||||||
|
use blob::__cmd__put_blob;
|
||||||
|
use blob::__cmd__get_blob;
|
||||||
|
|
||||||
|
use crate::{commands::{workspace::{create_workspace, update_y_document}, blob::{put_blob, get_blob}}};
|
||||||
|
|
||||||
|
pub fn invoke_handler() -> impl Fn(tauri::Invoke) + Send + Sync + 'static {
|
||||||
|
tauri::generate_handler![update_y_document, create_workspace, put_blob, get_blob]
|
||||||
|
}
|
||||||
68
client-app/src-tauri/src/commands/blob.rs
Normal file
68
client-app/src-tauri/src/commands/blob.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
use bytes::Bytes;
|
||||||
|
use futures::{
|
||||||
|
stream::{self},
|
||||||
|
StreamExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
use ipc_types::blob::{GetBlob, PutBlob};
|
||||||
|
use jwst::BlobStorage;
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn put_blob<'s>(
|
||||||
|
state: tauri::State<'s, AppState>,
|
||||||
|
parameters: PutBlob,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let blob_storage = &state.0.lock().await.blob_storage;
|
||||||
|
if let Ok(path) = blob_storage
|
||||||
|
.put_blob(
|
||||||
|
// TODO: ask octobase to accept blob directly or wrap/await tauri command to create a real stream, so we don't need to construct stream manually
|
||||||
|
Some(parameters.workspace_id.to_string()),
|
||||||
|
stream::iter::<Vec<Bytes>>(vec![Bytes::from(parameters.blob)]),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(path)
|
||||||
|
} else {
|
||||||
|
Err("Failed to create".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_blob<'s>(
|
||||||
|
state: tauri::State<'s, AppState>,
|
||||||
|
parameters: GetBlob,
|
||||||
|
) -> Result<Vec<u8>, String> {
|
||||||
|
let GetBlob { workspace_id, id } = parameters;
|
||||||
|
// TODO: check user permission? Or just assume there will only be one user
|
||||||
|
let blob_storage = &state.0.lock().await.blob_storage;
|
||||||
|
if let Ok(mut file_stream) = blob_storage.get_blob(Some(workspace_id.to_string()), id.clone()).await {
|
||||||
|
// Read all of the chunks into a vector.
|
||||||
|
let mut stream_contents = Vec::new();
|
||||||
|
let mut error_message = "".to_string();
|
||||||
|
while let Some(chunk) = file_stream.next().await {
|
||||||
|
match chunk {
|
||||||
|
Ok(chunk_bytes) => stream_contents.extend_from_slice(&chunk_bytes),
|
||||||
|
Err(err) => {
|
||||||
|
error_message = format!(
|
||||||
|
"Failed to read blob file {}/{} from stream, error: {}",
|
||||||
|
workspace_id.to_string(),
|
||||||
|
id,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if error_message.len() > 0 {
|
||||||
|
return Err(error_message);
|
||||||
|
}
|
||||||
|
Ok(stream_contents)
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
"Failed to read blob file {}/{} ",
|
||||||
|
workspace_id.to_string(),
|
||||||
|
id
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
38
client-app/src-tauri/src/commands/workspace.rs
Normal file
38
client-app/src-tauri/src/commands/workspace.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
use ipc_types::{document::YDocumentUpdate, workspace::CreateWorkspace};
|
||||||
|
use jwst::{DocStorage, Workspace};
|
||||||
|
use lib0::any::Any;
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn create_workspace<'s>(
|
||||||
|
state: tauri::State<'s, AppState>,
|
||||||
|
parameters: CreateWorkspace,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
let workspace = Workspace::new(parameters.id.to_string());
|
||||||
|
workspace.with_trx(|mut workspace_transaction| {
|
||||||
|
// TODO: why this Any here?
|
||||||
|
workspace_transaction.set_metadata("name", Any::String(parameters.name.into_boxed_str()));
|
||||||
|
workspace_transaction.set_metadata(
|
||||||
|
"avatar",
|
||||||
|
Any::String(parameters.avatar.clone().into_boxed_str()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if let Err(error_message) = &state
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.doc_storage
|
||||||
|
.write_doc(parameters.id, workspace.doc())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(error_message.to_string())
|
||||||
|
} else {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn update_y_document(parameters: YDocumentUpdate) -> Result<bool, String> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
35
client-app/src-tauri/src/main.rs
Normal file
35
client-app/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#![cfg_attr(
|
||||||
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
|
windows_subsystem = "windows"
|
||||||
|
)]
|
||||||
|
|
||||||
|
mod commands;
|
||||||
|
mod state;
|
||||||
|
use state::AppState;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tauri::TitleBarStyle;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tauri::async_runtime::set(tokio::runtime::Handle::current());
|
||||||
|
let preload = include_str!("../../public/preload/index.js");
|
||||||
|
tauri::Builder::default()
|
||||||
|
.manage(AppState(Mutex::new(
|
||||||
|
state::AppStateRaw::new().await.unwrap(),
|
||||||
|
)))
|
||||||
|
// manually create window here, instead of in the tauri.conf.json, to add `initialization_script` here
|
||||||
|
.setup(move |app| {
|
||||||
|
let _window =
|
||||||
|
tauri::WindowBuilder::new(app, "label", tauri::WindowUrl::App("index.html".into()))
|
||||||
|
.title("AFFiNE")
|
||||||
|
.inner_size(1000.0, 800.0)
|
||||||
|
.title_bar_style(TitleBarStyle::Overlay)
|
||||||
|
.hidden_title(true)
|
||||||
|
.initialization_script(&preload)
|
||||||
|
.build()?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.invoke_handler(commands::invoke_handler())
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
39
client-app/src-tauri/src/state.rs
Normal file
39
client-app/src-tauri/src/state.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use jwst_storage::{BlobFsStorage, DBContext, DocFsStorage};
|
||||||
|
use std::path::Path;
|
||||||
|
use tauri::api::path::desktop_dir;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
pub struct AppStateRaw {
|
||||||
|
pub blob_storage: BlobFsStorage,
|
||||||
|
pub doc_storage: DocFsStorage,
|
||||||
|
pub metadata_db: DBContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppStateRaw {
|
||||||
|
pub async fn new() -> Option<AppStateRaw> {
|
||||||
|
let doc_env = Path::new(&desktop_dir()?.into_os_string())
|
||||||
|
.join("affine-dev")
|
||||||
|
.join("doc");
|
||||||
|
let blob_env = Path::new(&(desktop_dir()?.into_os_string()))
|
||||||
|
.join("affine-dev")
|
||||||
|
.join("blob");
|
||||||
|
let db_env = format!(
|
||||||
|
"sqlite://{}?mode=rwc",
|
||||||
|
Path::new(&(desktop_dir()?.into_os_string()))
|
||||||
|
.join("affine-dev")
|
||||||
|
.join("db")
|
||||||
|
.join("metadata.db")
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
doc_storage: DocFsStorage::new(Some(16), 500, Path::new(&doc_env).into()).await,
|
||||||
|
blob_storage: BlobFsStorage::new(Some(16), Path::new(&blob_env).into()).await,
|
||||||
|
metadata_db: DBContext::new(db_env).await,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppState(pub Mutex<AppStateRaw>); // need pub, otherwise will be "field `0` of struct `types::state::AppState` is private"
|
||||||
58
client-app/src-tauri/tauri.conf.json
Normal file
58
client-app/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "pnpm dev:web",
|
||||||
|
"beforeBuildCommand": "pnpm build:web",
|
||||||
|
"devPath": "http://localhost:1420",
|
||||||
|
"distDir": "../dist",
|
||||||
|
"withGlobalTauri": false
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
"productName": "AFFiNE",
|
||||||
|
"version": "0.0.2"
|
||||||
|
},
|
||||||
|
"tauri": {
|
||||||
|
"allowlist": {
|
||||||
|
"all": true,
|
||||||
|
"fs": {
|
||||||
|
"all": true,
|
||||||
|
"scope": ["$RESOURCE", "$RESOURCE/*", "$APP/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"category": "DeveloperTool",
|
||||||
|
"copyright": "",
|
||||||
|
"deb": {
|
||||||
|
"depends": []
|
||||||
|
},
|
||||||
|
"externalBin": [],
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"identifier": "com.affine.client",
|
||||||
|
"longDescription": "",
|
||||||
|
"macOS": {
|
||||||
|
"entitlements": null,
|
||||||
|
"exceptionDomain": "",
|
||||||
|
"frameworks": [],
|
||||||
|
"providerShortName": null,
|
||||||
|
"signingIdentity": null
|
||||||
|
},
|
||||||
|
"resources": [],
|
||||||
|
"shortDescription": "",
|
||||||
|
"targets": "all",
|
||||||
|
"windows": {
|
||||||
|
"certificateThumbprint": null,
|
||||||
|
"digestAlgorithm": "sha256",
|
||||||
|
"timestampUrl": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updater": {
|
||||||
|
"active": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
client-app/src-tauri/types/Cargo.toml
Normal file
9
client-app/src-tauri/types/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "ipc_types"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
project-root = "0.2.2"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
schemars = "0.8.3"
|
||||||
20
client-app/src-tauri/types/src/blob.rs
Normal file
20
client-app/src-tauri/types/src/blob.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct PutBlob {
|
||||||
|
pub workspace_id: u64,
|
||||||
|
pub blob: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct GetBlob {
|
||||||
|
pub workspace_id: u64,
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub enum IBlobParameters {
|
||||||
|
Put(PutBlob),
|
||||||
|
Get(GetBlob),
|
||||||
|
}
|
||||||
8
client-app/src-tauri/types/src/document.rs
Normal file
8
client-app/src-tauri/types/src/document.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct YDocumentUpdate<'a> {
|
||||||
|
update: Vec<u8>,
|
||||||
|
room: &'a str,
|
||||||
|
}
|
||||||
7
client-app/src-tauri/types/src/lib.rs
Normal file
7
client-app/src-tauri/types/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#[allow(unused_imports)]
|
||||||
|
extern crate serde;
|
||||||
|
extern crate schemars;
|
||||||
|
|
||||||
|
pub mod blob;
|
||||||
|
pub mod document;
|
||||||
|
pub mod workspace;
|
||||||
9
client-app/src-tauri/types/src/workspace.rs
Normal file
9
client-app/src-tauri/types/src/workspace.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct CreateWorkspace {
|
||||||
|
pub id: i64,
|
||||||
|
pub name: String,
|
||||||
|
pub avatar: String,
|
||||||
|
}
|
||||||
38
client-app/src/components/TitleBar.tsx
Normal file
38
client-app/src/components/TitleBar.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { routes } from '../pages/routes.js';
|
||||||
|
|
||||||
|
const Container = styled.nav`
|
||||||
|
height: var(--title-bar-height);
|
||||||
|
background: #329ea3;
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
`;
|
||||||
|
const RouteSelect = styled.select`
|
||||||
|
margin-left: 100px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function TitleBar() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<Container data-tauri-drag-region>
|
||||||
|
<RouteSelect
|
||||||
|
onChange={event => {
|
||||||
|
navigate(event?.target?.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{routes.map(route => (
|
||||||
|
<option key={route.path} value={route.path}>
|
||||||
|
{route.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</RouteSelect>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
54
client-app/src/ipc/TauriIPCProvider.ts
Normal file
54
client-app/src/ipc/TauriIPCProvider.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
import { Observable } from 'lib0/observable';
|
||||||
|
import { DocProvider } from '@blocksuite/store';
|
||||||
|
import type { Awareness } from 'y-protocols/awareness';
|
||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { updateYDocument } from './methods';
|
||||||
|
|
||||||
|
export class TauriIPCProvider
|
||||||
|
extends Observable<string>
|
||||||
|
implements DocProvider
|
||||||
|
{
|
||||||
|
#yDocument: Y.Doc;
|
||||||
|
constructor(
|
||||||
|
room: string,
|
||||||
|
yDocument: Y.Doc,
|
||||||
|
options?: { awareness?: Awareness }
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.#yDocument = yDocument;
|
||||||
|
this.#yDocument.on(
|
||||||
|
'updateV2',
|
||||||
|
async (
|
||||||
|
update: Uint8Array,
|
||||||
|
origin: any,
|
||||||
|
_yDocument: Y.Doc,
|
||||||
|
_transaction: Y.Transaction
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
// TODO: need handle potential data race when update is frequent?
|
||||||
|
// TODO: update seems too frequent upon each keydown, why no batching?
|
||||||
|
const success = await updateYDocument({
|
||||||
|
update: Array.from(update),
|
||||||
|
room,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// TODO: write error log to disk, and add button to open them in settings panel
|
||||||
|
console.error("#yDocument.on('updateV2'", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {}
|
||||||
|
|
||||||
|
destroy() {}
|
||||||
|
disconnect() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearData() {}
|
||||||
|
}
|
||||||
24
client-app/src/ipc/methods.ts
Normal file
24
client-app/src/ipc/methods.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { YDocumentUpdate } from '../types/ipc/document';
|
||||||
|
import { CreateWorkspace } from '../types/ipc/workspace';
|
||||||
|
import { GetBlob, PutBlob } from '../types/ipc/blob';
|
||||||
|
|
||||||
|
export const updateYDocument = async (parameters: YDocumentUpdate) =>
|
||||||
|
await invoke<boolean>('update_y_document', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createWorkspace = async (parameters: CreateWorkspace) =>
|
||||||
|
await invoke<boolean>('create_workspace', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const putBlob = async (parameters: PutBlob) =>
|
||||||
|
await invoke<string>('put_blob', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getBlob = async (parameters: GetBlob) =>
|
||||||
|
await invoke<number[]>('get_blob', {
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
16
client-app/src/main.css
Normal file
16
client-app/src/main.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
:root {
|
||||||
|
--title-bar-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: var(--title-bar-height);
|
||||||
|
height: calc(100vh - var(--title-bar-height));
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#react-root {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
15
client-app/src/main.tsx
Normal file
15
client-app/src/main.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import '@emotion/react';
|
||||||
|
import { StrictMode } from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { RouterProvider } from 'react-router-dom';
|
||||||
|
import { router } from './pages/routes.js';
|
||||||
|
import './main.css';
|
||||||
|
|
||||||
|
const root = document.querySelector('#react-root');
|
||||||
|
if (root !== null) {
|
||||||
|
ReactDOM.createRoot(root).render(
|
||||||
|
<StrictMode>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
client-app/src/pages/AFFiNE/index.tsx
Normal file
10
client-app/src/pages/AFFiNE/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { redirect } from 'react-router-dom';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
export function AffineBasicPage() {
|
||||||
|
useEffect(() => {
|
||||||
|
location.href = '/affine-out/index.html';
|
||||||
|
redirect('/affine-out/index.html');
|
||||||
|
}, []);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
3
client-app/src/pages/Landing/index.tsx
Normal file
3
client-app/src/pages/Landing/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function LandingPage() {
|
||||||
|
return <div>TBD</div>;
|
||||||
|
}
|
||||||
11
client-app/src/pages/Root.tsx
Normal file
11
client-app/src/pages/Root.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { TitleBar } from '../components/TitleBar.js';
|
||||||
|
|
||||||
|
export function RootLayout() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TitleBar />
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
client-app/src/pages/routes.tsx
Normal file
20
client-app/src/pages/routes.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { createBrowserRouter } from 'react-router-dom';
|
||||||
|
import { AffineBasicPage } from './AFFiNE/index.js';
|
||||||
|
import { LandingPage } from './Landing/index.js';
|
||||||
|
import { RootLayout } from './Root.js';
|
||||||
|
|
||||||
|
export const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Landing Page',
|
||||||
|
element: <LandingPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/affine',
|
||||||
|
name: 'AFFiNE Basic',
|
||||||
|
element: <AffineBasicPage />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const router = createBrowserRouter([
|
||||||
|
{ path: '/', element: <RootLayout />, children: routes },
|
||||||
|
]);
|
||||||
5
client-app/src/preload/Readme.md
Normal file
5
client-app/src/preload/Readme.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Preload Scripts
|
||||||
|
|
||||||
|
Here are preload scripts (See [tauri's doc](https://tauri.app/v1/references/architecture/inter-process-communication/isolation)). This is simillar to Electron's [preload script](https://www.electronjs.org/docs/latest/tutorial/sandbox#preload-scripts).
|
||||||
|
|
||||||
|
We pass env variables to AFFiNE side from here.
|
||||||
18
client-app/src/preload/index.ts
Normal file
18
client-app/src/preload/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
|
||||||
|
// tauri preload script can't have `export {}`
|
||||||
|
// @ts-ignore 'index.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.ts(1208)
|
||||||
|
window.__TAURI_ISOLATION_HOOK__ = payload => {
|
||||||
|
console.log('Tauri isolation hook', payload);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give AFFiNE app code some env to know it is inside a tauri app.
|
||||||
|
*/
|
||||||
|
function setEnvironmentVariables() {
|
||||||
|
window.CLIENT_APP = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnvironmentVariables();
|
||||||
23
client-app/src/types/affine.ts
Normal file
23
client-app/src/types/affine.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copied from packages/data-services/src/sdks/workspace.ts
|
||||||
|
* // TODO: after it published, use that package
|
||||||
|
*/
|
||||||
|
export enum WorkspaceType {
|
||||||
|
Private = 0,
|
||||||
|
Normal = 1,
|
||||||
|
}
|
||||||
|
export enum PermissionType {
|
||||||
|
Read = 0,
|
||||||
|
Write = 1,
|
||||||
|
Admin = 2,
|
||||||
|
Owner = 3,
|
||||||
|
}
|
||||||
|
export interface Workspace {
|
||||||
|
avatar: string;
|
||||||
|
id: number;
|
||||||
|
create_at: number;
|
||||||
|
name: string;
|
||||||
|
permission_type: PermissionType;
|
||||||
|
public: boolean;
|
||||||
|
type: WorkspaceType;
|
||||||
|
}
|
||||||
11
client-app/src/types/index.ts
Normal file
11
client-app/src/types/index.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// TODO: find official typings if available
|
||||||
|
type IsolationPayload = unknown;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
CLIENT_APP?: boolean;
|
||||||
|
__TAURI_ISOLATION_HOOK__: (payload: IsolationPayload) => IsolationPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
61
client-app/src/types/ipc/blob.json
Normal file
61
client-app/src/types/ipc/blob.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "IBlobParameters",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["Put"],
|
||||||
|
"properties": {
|
||||||
|
"Put": {
|
||||||
|
"$ref": "#/definitions/PutBlob"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["Get"],
|
||||||
|
"properties": {
|
||||||
|
"Get": {
|
||||||
|
"$ref": "#/definitions/GetBlob"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"definitions": {
|
||||||
|
"GetBlob": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "workspace_id"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint64",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PutBlob": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["blob", "workspace_id"],
|
||||||
|
"properties": {
|
||||||
|
"blob": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint64",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
client-app/src/types/ipc/blob.ts
Normal file
25
client-app/src/types/ipc/blob.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/**
|
||||||
|
* This file was automatically generated by json-schema-to-typescript.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||||
|
* and run json-schema-to-typescript to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type IBlobParameters =
|
||||||
|
| {
|
||||||
|
Put: PutBlob;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
Get: GetBlob;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PutBlob {
|
||||||
|
[k: string]: unknown;
|
||||||
|
blob: number[];
|
||||||
|
workspace_id: number;
|
||||||
|
}
|
||||||
|
export interface GetBlob {
|
||||||
|
[k: string]: unknown;
|
||||||
|
id: string;
|
||||||
|
workspace_id: number;
|
||||||
|
}
|
||||||
19
client-app/src/types/ipc/document.json
Normal file
19
client-app/src/types/ipc/document.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "YDocumentUpdate",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["room", "update"],
|
||||||
|
"properties": {
|
||||||
|
"room": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
client-app/src/types/ipc/document.ts
Normal file
12
client-app/src/types/ipc/document.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/**
|
||||||
|
* This file was automatically generated by json-schema-to-typescript.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||||
|
* and run json-schema-to-typescript to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface YDocumentUpdate {
|
||||||
|
[k: string]: unknown;
|
||||||
|
room: string;
|
||||||
|
update: number[];
|
||||||
|
}
|
||||||
18
client-app/src/types/ipc/workspace.json
Normal file
18
client-app/src/types/ipc/workspace.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "CreateWorkspace",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["avatar", "id", "name"],
|
||||||
|
"properties": {
|
||||||
|
"avatar": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
client-app/src/types/ipc/workspace.ts
Normal file
13
client-app/src/types/ipc/workspace.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/**
|
||||||
|
* This file was automatically generated by json-schema-to-typescript.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
||||||
|
* and run json-schema-to-typescript to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CreateWorkspace {
|
||||||
|
[k: string]: unknown;
|
||||||
|
avatar: string;
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
38
client-app/src/utils/getAvatar.ts
Normal file
38
client-app/src/utils/getAvatar.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const DefaultHeadImgColors = [
|
||||||
|
['#C6F2F3', '#0C6066'],
|
||||||
|
['#FFF5AB', '#896406'],
|
||||||
|
['#FFCCA7', '#8F4500'],
|
||||||
|
['#FFCECE', '#AF1212'],
|
||||||
|
['#E3DEFF', '#511AAB'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO: move this back to AFFiNE repo
|
||||||
|
export async function createDefaultUserAvatar(
|
||||||
|
workspaceName: string
|
||||||
|
): Promise<Blob> {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.height = 100;
|
||||||
|
canvas.width = 100;
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
if (context === null) {
|
||||||
|
throw new Error('Failed to create avatar canvas');
|
||||||
|
}
|
||||||
|
const randomNumber = Math.floor(Math.random() * 5);
|
||||||
|
const randomColor = DefaultHeadImgColors[randomNumber];
|
||||||
|
context.fillStyle = randomColor[0];
|
||||||
|
context.fillRect(0, 0, 100, 100);
|
||||||
|
context.font = "600 50px 'PingFang SC', 'Microsoft Yahei'";
|
||||||
|
context.fillStyle = randomColor[1];
|
||||||
|
context.textAlign = 'center';
|
||||||
|
context.textBaseline = 'middle';
|
||||||
|
context.fillText(workspaceName[0], 50, 50);
|
||||||
|
return await new Promise<Blob>((resolve, reject) => {
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
if (blob === null) {
|
||||||
|
reject(new Error('Failed to convert avatar canvas to blob'));
|
||||||
|
} else {
|
||||||
|
resolve(blob);
|
||||||
|
}
|
||||||
|
}, 'image/png');
|
||||||
|
});
|
||||||
|
}
|
||||||
28
client-app/tsconfig.json
Normal file
28
client-app/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||||
|
"moduleResolution": "Node" /* can't use NodeNext, otherwise can't find styled-components and @emotion/styled 's type, because ts won't follow `type` field in @emotion/styled 's packagejson, will follow `main` instead */,
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": true /* Enable strict null checks. */,
|
||||||
|
"strictFunctionTypes": true /* Enable strict checking of function types. */,
|
||||||
|
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
|
||||||
|
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
|
||||||
|
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"jsx": "react-jsx" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
|
||||||
|
"allowJs": false /* Allow javascript files to be compiled. */,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"typeRoots": ["types"],
|
||||||
|
"noEmit": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noImplicitReturns": true
|
||||||
|
},
|
||||||
|
"include": ["./src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
27
client-app/vite.config.ts
Normal file
27
client-app/vite.config.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
|
// prevent vite from obscuring rust errors
|
||||||
|
clearScreen: false,
|
||||||
|
// tauri expects a fixed port, fail if that port is not available
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
},
|
||||||
|
// to make use of `TAURI_DEBUG` and other env variables
|
||||||
|
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
||||||
|
envPrefix: ['VITE_', 'TAURI_'],
|
||||||
|
build: {
|
||||||
|
// Tauri supports es2021
|
||||||
|
target: ['es2021', 'chrome100', 'safari13'],
|
||||||
|
// don't minify for debug builds
|
||||||
|
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
|
||||||
|
// produce sourcemaps for debug builds
|
||||||
|
sourcemap: !!process.env.TAURI_DEBUG,
|
||||||
|
},
|
||||||
|
esbuild: {
|
||||||
|
jsxInject: `import React from 'react';`,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -7,7 +7,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
|
"dev": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
|
||||||
"dev:ac": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev:ac",
|
"dev:ac": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev:ac",
|
||||||
|
"dev:client": "pnpm --filter=@affine/client-app dev:app",
|
||||||
"build": " pnpm --filter=!@affine/app build && pnpm --filter!=@affine/datacenter -r build",
|
"build": " pnpm --filter=!@affine/app build && pnpm --filter!=@affine/datacenter -r build",
|
||||||
|
"build:client": " pnpm --filter=@affine/client-app build",
|
||||||
"export": "pnpm --filter @affine/app export",
|
"export": "pnpm --filter @affine/app export",
|
||||||
"start": "pnpm --filter @affine/app start",
|
"start": "pnpm --filter @affine/app start",
|
||||||
"lint": "pnpm --filter @affine/app lint",
|
"lint": "pnpm --filter @affine/app lint",
|
||||||
|
|||||||
2206
pnpm-lock.yaml
generated
2206
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
|||||||
packages:
|
packages:
|
||||||
# all packages in direct subdirs of packages/
|
# all packages in direct subdirs of packages/
|
||||||
- 'packages/*'
|
- 'packages/*'
|
||||||
|
# app folder is on top level because of its importance
|
||||||
|
- 'client-app'
|
||||||
|
|||||||
Reference in New Issue
Block a user