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.formatOnSave": true,
|
||||
"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": {
|
||||
"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:client": "pnpm --filter=@affine/client-app dev:app",
|
||||
"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",
|
||||
"start": "pnpm --filter @affine/app start",
|
||||
"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:
|
||||
# all packages in direct subdirs of packages/
|
||||
- 'packages/*'
|
||||
# app folder is on top level because of its importance
|
||||
- 'client-app'
|
||||
|
||||
Reference in New Issue
Block a user