Compare commits

..

4 Commits

Author SHA1 Message Date
Anselm
b9007dd1b5 Publish
- jazz-example-todo@0.0.4
 - cojson@0.0.18
 - jazz-react@0.0.10
 - jazz-react-auth-local@0.0.7
 - jazz-storage-indexeddb@0.0.5
2023-08-17 13:43:46 +01:00
Anselm
ee1e5b06e4 Fix bundling of packages 2023-08-17 13:43:30 +01:00
Anselm
fae290c4cf Add exports to cojson 2023-08-17 12:36:58 +01:00
Anselm
381d68019f Try deploying jazz-example 2023-08-17 12:33:28 +01:00
497 changed files with 22263 additions and 65733 deletions

View File

@@ -1,8 +0,0 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@@ -1,24 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [
[
"cojson",
"jazz-tools",
"jazz-browser",
"jazz-browser-media-images",
"jazz-react",
"jazz-nodejs",
"jazz-run",
"cojson-transport-ws",
"cojson-storage-indexeddb",
"cojson-storage-sqlite"
]
],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}

View File

@@ -3,66 +3,51 @@ name: Build and Deploy
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build-examples:
build-and-deploy:
runs-on: ubuntu-latest
strategy:
matrix:
example: ["chat", "pets", "todo", "inspector"]
# example: ["twit", "chat", "counter-js-auth0", "pets", "twit", "file-drop", "inspector"]
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'pnpm'
cache: 'yarn'
cache-dependency-path: yarn.lock
- name: Yarn Build
run: |
yarn install --frozen-lockfile;
yarn build;
working-directory: ./examples/todo
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- uses: satackey/action-docker-layer-caching@v0.0.11
continue-on-error: true
with:
key: docker-layer-caching-${{ github.workflow }}-{hash}
restore-keys: |
docker-layer-caching-${{ github.workflow }}-
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v1
with:
registry: ghcr.io
username: gardencmp
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pnpm Build
run: |
pnpm install
pnpm turbo build;
working-directory: ./examples/${{ matrix.example }}
- name: Docker Build & Push
uses: docker/build-push-action@v4
with:
context: ./examples/${{ matrix.example }}
push: true
tags: ghcr.io/gardencmp/${{github.event.repository.name}}-example-${{ matrix.example }}:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}}
cache-from: type=gha
cache-to: type=gha,mode=max
run: |
export DOCKER_TAG=ghcr.io/gardencmp/jazz-example-todo:${{github.head_ref || github.ref_name}}-${{github.sha}}-$(date +%s) ;
docker build . --file Dockerfile --tag $DOCKER_TAG;
docker push $DOCKER_TAG;
echo "DOCKER_TAG=$DOCKER_TAG" >> $GITHUB_ENV
working-directory: ./examples/todo
deploy-examples:
runs-on: ubuntu-latest
needs: build-examples
strategy:
matrix:
example: ["chat", "pets", "todo", "inspector"]
# example: ["twit", "chat", "counter-js-auth0", "pets", "twit", "file-drop", "inspector"]
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: gacts/install-nomad@v1
- name: Tailscale
uses: tailscale/github-action@v1
@@ -81,9 +66,13 @@ jobs:
export DOCKER_USER=gardencmp;
export DOCKER_PASSWORD=${{ secrets.DOCKER_PULL_PAT }};
export DOCKER_TAG=ghcr.io/gardencmp/${{github.event.repository.name}}-example-${{ matrix.example }}:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}};
export DOCKER_TAG=${{ env.DOCKER_TAG }};
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
cat job-instance.nomad;
NOMAD_ADDR=${{ secrets.NOMAD_ADDR }} nomad job run job-instance.nomad;
working-directory: ./examples/${{ matrix.example }}
for region in ${{ vars.DEPLOY_REGIONS }}
do
export REGION=$region;
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN} ${REGION}' < job-template.nomad > job-instance.nomad;
cat job-instance.nomad;
NOMAD_ADDR='${{ secrets.NOMAD_ADDR }}' nomad job run job-instance.nomad;
done
working-directory: ./examples/todo

5
.gitignore vendored
View File

@@ -1,6 +1,3 @@
node_modules
yarn-error.log
lerna-debug.log
docsTmp
.DS_Store
.turbo
lerna-debug.log

View File

@@ -1,19 +0,0 @@
Copyright 2024, Garden Computing, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,12 +1,9 @@
# Jazz - Instant sync
# Jazz - instant sync
Jazz is an open-source toolkit for telepathic data.
Ship faster and simplify frontend, backend & devops by building with Telepathic Data.
Get real-time multiplayer and cross-device sync for free.
**Jazz is an open-source toolkit for building apps with *distributed state.***
- Homepage: [jazz.tools](https://jazz.tools)
- Docs: [jazz.tools/docs](https://jazz.tools/docs)
- Community & support: [Discord](https://discord.gg/utDMjHYg42)
- Updates: [Twitter](https://twitter.com/jazz_tools) & [Email](https://gcmp.io/news)
Copyright 2024 &mdash; Garden Computing, Inc.
## What is Telepathic Data?
...

View File

@@ -1,13 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {},
}

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,840 +0,0 @@
# jazz-example-chat
## 0.0.81
### Patch Changes
- Updated dependencies [5d91f9f]
- Updated dependencies [5094e6d]
- Updated dependencies [b09589b]
- Updated dependencies [2c3a40c]
- Updated dependencies [4e16575]
- Updated dependencies [ea882ab]
- cojson@0.7.34
- jazz-react@0.7.34
- jazz-tools@0.7.34
## 0.0.81-neverthrow.6
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- jazz-react@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.0.81-neverthrow.5
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- jazz-react@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.0.81-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- jazz-react@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.0.81-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- jazz-react@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.0.81-neverthrow.2
### Patch Changes
- jazz-react@0.7.34-neverthrow.2
## 0.0.81-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- jazz-react@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.0.81-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- jazz-react@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.0.80
### Patch Changes
- Updated dependencies [b297c93]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson@0.7.33
- jazz-react@0.7.33
- jazz-tools@0.7.33
## 0.0.80-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- jazz-react@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.0.80-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- jazz-react@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.0.80-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.3
- jazz-react@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.0.80-hotfixes.2
### Patch Changes
- jazz-react@0.7.33-hotfixes.2
## 0.0.80-hotfixes.1
### Patch Changes
- jazz-react@0.7.33-hotfixes.1
## 0.0.80-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- jazz-react@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.0.79
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
- jazz-react@0.7.32
## 0.0.78
### Patch Changes
- Updated dependencies
- cojson@0.7.31
- jazz-react@0.7.31
- jazz-tools@0.7.31
## 0.0.77
### Patch Changes
- jazz-react@0.7.30
## 0.0.76
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- jazz-react@0.7.29
- jazz-tools@0.7.29
## 0.0.75
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- jazz-react@0.7.28
- jazz-tools@0.7.28
## 0.0.74
### Patch Changes
- jazz-react@0.7.27
## 0.0.73
### Patch Changes
- Updated dependencies
- cojson@0.7.26
- jazz-react@0.7.26
- jazz-tools@0.7.26
## 0.0.72
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
- jazz-react@0.7.25
## 0.0.71
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.24
- jazz-react@0.7.24
## 0.0.70
### Patch Changes
- Updated dependencies
- cojson@0.7.23
- jazz-react@0.7.23
- jazz-tools@0.7.23
## 0.0.69
### Patch Changes
- jazz-react@0.7.22
## 0.0.68
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.21
- jazz-react@0.7.21
## 0.0.67
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.20
- jazz-react@0.7.20
## 0.0.66
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.19
- jazz-react@0.7.19
## 0.0.65
### Patch Changes
- Updated dependencies
- cojson@0.7.18
- jazz-react@0.7.18
- jazz-tools@0.7.18
## 0.0.64
### Patch Changes
- Updated dependencies
- cojson@0.7.17
- jazz-react@0.7.17
- jazz-tools@0.7.17
## 0.0.63
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.16
- jazz-react@0.7.16
## 0.0.62
### Patch Changes
- Updated dependencies
- jazz-react@0.7.15
## 0.0.61
### Patch Changes
- Updated dependencies
- cojson@0.7.14
- jazz-tools@0.7.14
- jazz-react@0.7.14
## 0.0.60
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.13
- jazz-react@0.7.13
## 0.0.59
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.12
- jazz-react@0.7.12
## 0.0.58
### Patch Changes
- Updated dependencies
- cojson@0.7.11
- jazz-react@0.7.11
- jazz-tools@0.7.11
## 0.0.57
### Patch Changes
- Updated dependencies
- cojson@0.7.10
- jazz-react@0.7.10
- jazz-tools@0.7.10
## 0.0.56
### Patch Changes
- Updated dependencies
- cojson@0.7.9
- jazz-react@0.7.9
- jazz-tools@0.7.9
## 0.0.55
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.8
- jazz-react@0.7.8
## 0.0.54
### Patch Changes
- Updated dependencies [9fdc91c]
- jazz-react@0.7.7
## 0.0.53
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.6
- jazz-react@0.7.6
## 0.0.52
### Patch Changes
- Updated dependencies
- jazz-react@0.7.5
## 0.0.51
### Patch Changes
- Updated dependencies
- jazz-react@0.7.4
## 0.0.50
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.3
- jazz-react@0.7.3
## 0.0.49
### Patch Changes
- Updated dependencies
- jazz-react@0.7.2
## 0.0.48
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.1
- jazz-react@0.7.1
## 0.0.47
### Patch Changes
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [96c494f]
- Updated dependencies [59c18c3]
- Updated dependencies [19f52b7]
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [d8fe2b1]
- Updated dependencies [19004b4]
- Updated dependencies [a78f168]
- Updated dependencies [1200aae]
- Updated dependencies [60d5ca2]
- Updated dependencies [52675c9]
- Updated dependencies [129e2c1]
- Updated dependencies [6d49e9b]
- Updated dependencies [1cfa279]
- Updated dependencies [704af7d]
- Updated dependencies [e97f730]
- Updated dependencies [1a35307]
- Updated dependencies [460478f]
- Updated dependencies [6b0418f]
- Updated dependencies [e299c3e]
- Updated dependencies [ed5643a]
- Updated dependencies [bde684f]
- Updated dependencies [bf0f8ec]
- Updated dependencies [c4151fc]
- Updated dependencies [63374cc]
- Updated dependencies [8636319]
- Updated dependencies [01ac646]
- Updated dependencies [a5e68a4]
- Updated dependencies [8636319]
- Updated dependencies [952982e]
- Updated dependencies [1a35307]
- Updated dependencies [5fa277c]
- Updated dependencies [60d5ca2]
- Updated dependencies [21771c4]
- Updated dependencies [77c2b56]
- Updated dependencies [63374cc]
- Updated dependencies [d2e03ff]
- Updated dependencies [354bdcd]
- Updated dependencies [ece35b3]
- Updated dependencies [60d5ca2]
- Updated dependencies [69ac514]
- Updated dependencies [f8a5c46]
- Updated dependencies [f0f6f1b]
- Updated dependencies [e5eed5b]
- Updated dependencies [1a44f87]
- Updated dependencies [627d895]
- Updated dependencies [1200aae]
- Updated dependencies [63374cc]
- Updated dependencies [ece35b3]
- Updated dependencies [38d4410]
- Updated dependencies [85d2b62]
- Updated dependencies [fd86c11]
- Updated dependencies [52675c9]
- jazz-tools@0.7.0
- cojson@0.7.0
- jazz-react@0.7.0
- hash-slash@0.2.0
## 0.0.47-alpha.42
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.42
- cojson@0.7.0-alpha.42
- jazz-react@0.7.0-alpha.42
## 0.0.47-alpha.41
### Patch Changes
- jazz-tools@0.7.0-alpha.41
- jazz-react@0.7.0-alpha.41
## 0.0.47-alpha.40
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.40
## 0.0.47-alpha.39
### Patch Changes
- Updated dependencies
- cojson@0.7.0-alpha.39
- jazz-react@0.7.0-alpha.39
- jazz-tools@0.7.0-alpha.39
## 0.0.47-alpha.38
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.38
- jazz-react@0.7.0-alpha.38
- cojson@0.7.0-alpha.38
## 0.0.47-alpha.37
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.37
- cojson@0.7.0-alpha.37
- jazz-tools@0.7.0-alpha.37
## 0.0.47-alpha.36
### Patch Changes
- Updated dependencies [1a35307]
- Updated dependencies [1a35307]
- Updated dependencies [1a35307]
- Updated dependencies [1a35307]
- Updated dependencies [6b0418f]
- Updated dependencies [1a35307]
- cojson@0.7.0-alpha.36
- jazz-tools@0.7.0-alpha.36
- jazz-react@0.7.0-alpha.36
## 0.0.47-alpha.35
### Patch Changes
- Updated dependencies
- Updated dependencies
- cojson@0.7.0-alpha.35
- jazz-tools@0.7.0-alpha.35
- jazz-react@0.7.0-alpha.35
## 0.0.47-alpha.34
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.34
- jazz-react@0.7.0-alpha.34
## 0.0.47-alpha.33
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.33
## 0.0.47-alpha.32
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- hash-slash@0.2.0-alpha.3
- jazz-tools@0.7.0-alpha.32
- jazz-react@0.7.0-alpha.32
## 0.0.47-alpha.31
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.31
- jazz-react@0.7.0-alpha.31
## 0.0.47-alpha.30
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.30
- jazz-react@0.7.0-alpha.30
## 0.0.47-alpha.29
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.29
- cojson@0.7.0-alpha.29
- jazz-react@0.7.0-alpha.29
## 0.0.47-alpha.28
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.28
- cojson@0.7.0-alpha.28
- jazz-react@0.7.0-alpha.28
## 0.0.47-alpha.27
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.27
- cojson@0.7.0-alpha.27
- jazz-react@0.7.0-alpha.27
## 0.0.47-alpha.26
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.26
- jazz-react@0.7.0-alpha.26
## 0.0.47-alpha.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.25
- jazz-react@0.7.0-alpha.25
## 0.0.47-alpha.24
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.24
- cojson@0.7.0-alpha.24
- jazz-react@0.7.0-alpha.24
## 0.0.47-alpha.23
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.23
- jazz-react@0.7.0-alpha.23
## 0.0.47-alpha.22
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.22
- jazz-react@0.7.0-alpha.22
## 0.0.47-alpha.21
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.21
- jazz-tools@0.7.0-alpha.21
## 0.0.47-alpha.20
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-react@0.7.0-alpha.20
- jazz-tools@0.7.0-alpha.20
## 0.0.47-alpha.19
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.19
- jazz-react@0.7.0-alpha.19
## 0.0.47-alpha.18
### Patch Changes
- jazz-react@0.7.0-alpha.18
## 0.0.47-alpha.17
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.17
- jazz-react@0.7.0-alpha.17
## 0.0.47-alpha.16
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.16
- jazz-react@0.7.0-alpha.16
## 0.0.47-alpha.15
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.15
- jazz-react@0.7.0-alpha.15
## 0.0.47-alpha.14
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.14
- jazz-react@0.7.0-alpha.14
## 0.0.47-alpha.13
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.13
- jazz-react@0.7.0-alpha.13
## 0.0.47-alpha.12
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.12
- jazz-tools@0.7.0-alpha.12
## 0.0.47-alpha.11
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.11
- jazz-tools@0.7.0-alpha.11
- cojson@0.7.0-alpha.11
## 0.0.47-alpha.10
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.10
- jazz-tools@0.7.0-alpha.10
- cojson@0.7.0-alpha.10
## 0.0.47-alpha.9
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.9
- jazz-tools@0.7.0-alpha.9
## 0.0.47-alpha.8
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.8
- jazz-tools@0.7.0-alpha.8
## 0.0.47-alpha.7
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.7
- jazz-tools@0.7.0-alpha.7
- cojson@0.7.0-alpha.7
## 0.0.47-alpha.6
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.6
- jazz-tools@0.7.0-alpha.6
## 0.0.47-alpha.5
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.5
- jazz-tools@0.7.0-alpha.5
- cojson@0.7.0-alpha.5
## 0.0.47-alpha.4
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.4
- jazz-react@0.7.0-alpha.4
## 0.0.47-alpha.3
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.3
- jazz-react@0.7.0-alpha.3
## 0.0.47-alpha.2
### Patch Changes
- Updated dependencies
- hash-slash@0.2.0-alpha.2
- jazz-react@0.7.0-alpha.2
- jazz-tools@0.7.0-alpha.2
## 0.0.47-alpha.1
### Patch Changes
- Updated dependencies
- hash-slash@0.2.0-alpha.1
- jazz-react@0.7.0-alpha.1
- jazz-tools@0.7.0-alpha.1
- cojson@0.7.0-alpha.1
## 0.0.47-alpha.0
### Patch Changes
- Updated dependencies
- hash-slash@0.2.0-alpha.0
- jazz-react@0.7.0-alpha.0
- jazz-tools@0.7.0-alpha.0
- cojson@0.7.0-alpha.0
## 0.0.46
### Patch Changes
- Updated dependencies
- jazz-react@0.5.0
- jazz-react-auth-local@0.4.16

View File

@@ -1,4 +0,0 @@
FROM caddy:2.7.3-alpine
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
COPY ./dist /usr/share/caddy/

View File

@@ -1,42 +0,0 @@
# Jazz Chat Example
Live version: [https://chat.jazz.tools](https://chat.jazz.tools)
## Installing & running the example locally
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
Start by checking out `jazz`
```bash
git clone https://github.com/gardencmp/jazz.git
cd jazz/examples/chat
pnpm pack --pack-destination /tmp
mkdir -p ~/jazz-examples/chat # or any other directory
tar -xf /tmp/jazz-example-chat-* --strip-components 1 -C ~/jazz-examples/chat
cd ~/jazz-examples/chat
```
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
Install dependencies:
```bash
pnpm install
```
Start the dev server:
```bash
pnpm dev
```
## Questions / problems / feedback
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
## Configuration: sync server
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/2_main.tsx](./src/2_main.tsx).

View File

@@ -1,14 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Chat Example</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/app.tsx"></script>
</body>
</html>

View File

@@ -1,56 +0,0 @@
job "chat$BRANCH_SUFFIX" {
region = "global"
datacenters = ["*"]
group "static" {
count = 4
network {
port "http" {
to = 80
}
}
constraint {
attribute = "${node.class}"
operator = "="
value = "mesh"
}
spread {
attribute = "${node.datacenter}"
weight = 100
}
constraint {
distinct_hosts = true
}
task "server" {
driver = "docker"
config {
image = "$DOCKER_TAG"
ports = ["http"]
auth = {
username = "$DOCKER_USER"
password = "$DOCKER_PASSWORD"
}
}
service {
tags = ["public"]
name = "chat$BRANCH_SUFFIX"
port = "http"
provider = "consul"
}
resources {
cpu = 50 # MHz
memory = 50 # MB
}
}
}
}
# deploy bump 4

View File

@@ -1,54 +0,0 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.81",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "echo 'chat example is codegolfed'",
"preview": "vite preview"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,mdx,json}": "prettier --write"
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"@types/qrcode": "^1.5.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"hash-slash": "workspace:*",
"jazz-tools": "workspace:*",
"jazz-react": "workspace:*",
"cojson": "workspace:*",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"react-use": "^17.4.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uniqolor": "^1.1.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.27",
"tailwindcss": "^3.3.3",
"typescript": "^5.0.2",
"vite": "^5.0.10"
}
}

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,43 +0,0 @@
import { CoMap, CoList, co, Group, ID } from "jazz-tools";
import { createJazzReactContext, DemoAuth } from "jazz-react";
import { createRoot } from "react-dom/client";
import { useIframeHashRouter } from "hash-slash";
import { ChatScreen } from "./chatScreen.tsx";
import { StrictMode } from "react";
export class Message extends CoMap {
text = co.string;
}
export class Chat extends CoList.Of(co.ref(Message)) {}
const Jazz = createJazzReactContext({
auth: DemoAuth({ appName: "Jazz Chat" }),
peer: `wss://mesh.jazz.tools/?key=you@example.com`
});
export const { useAccount, useCoState } = Jazz;
function App() {
const { me, logOut } = useAccount();
const createChat = () => {
const group = Group.create({ owner: me });
group.addMember("everyone", "writer");
const chat = Chat.create([], { owner: group });
location.hash = "/chat/" + chat.id;
};
return <div className="flex flex-col items-center justify-between w-screen h-screen p-2 dark:bg-black dark:text-white">
<div className="rounded mb-5 px-2 py-1 text-sm self-end">
{me.profile?.name} · <button onClick={logOut}>Log Out</button>
</div>
{useIframeHashRouter().route({
'/': () => createChat() as never,
'/chat/:id': (id) => <ChatScreen chatID={id as ID<Chat>} />
})}
</div>;
}
createRoot(document.getElementById("root")!)
.render(<StrictMode><Jazz.Provider><App/></Jazz.Provider></StrictMode>);

View File

@@ -1,42 +0,0 @@
import { ID } from 'jazz-tools';
import { Chat, Message, useCoState } from './app.tsx';
export function ChatScreen(props: { chatID: ID<Chat> }) {
const chat = useCoState(Chat, props.chatID, [{}]);
return chat ? <div className='w-full max-w-xl h-full flex flex-col items-stretch'>
{chat.length > 0
? chat.map((msg) => <ChatBubble msg={msg} key={msg.id} />)
: <div className='m-auto text-sm'>(Empty chat)</div>}
<ChatInput onSubmit={(text) => {
chat.push(
Message.create({ text }, { owner: chat._owner })
);
}} />
</div> : <div>Loading...</div>;
}
function ChatBubble(props: { msg: Message }) {
const lastEdit = props.msg._edits.text;
const align = lastEdit.by?.isMe ? 'items-end' : 'items-start';
return <div className={`${align} flex flex-col`}>
<div className='rounded-xl bg-stone-100 dark:bg-stone-700 dark:text-white py-2 px-4 mt-2 min-w-[5rem]'>
{ props.msg.text }
</div>
<div className='text-xs text-neutral-500 ml-2'>
{ lastEdit.by?.profile?.name }{' '}
{ lastEdit.madeAt?.toLocaleTimeString() }
</div>
</div>;
}
function ChatInput(props: { onSubmit: (text: string) => void }) {
return <input className='rounded p-2 border mt-auto dark:bg-black dark:text-white border-stone-300 dark:border-stone-700'
placeholder='Type a message and press Enter'
onKeyDown={({ key, currentTarget: input }) => {
if (key !== 'Enter' || !input.value) return;
props.onSubmit(input.value);
input.value = '';
}} />;
}

View File

@@ -1,78 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 24 9.8% 10%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 60 9.1% 97.8%;
--primary-foreground: 24 9.8% 10%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 24 5.7% 82.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
margin: 0;
padding: 0;
}
}

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,75 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}

View File

@@ -1,29 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,16 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
minify: false
}
})

View File

@@ -1,18 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,224 +0,0 @@
# jazz-example-chat
## 0.0.59
### Patch Changes
- Updated dependencies [5d91f9f]
- Updated dependencies [5094e6d]
- Updated dependencies [b09589b]
- Updated dependencies [2c3a40c]
- Updated dependencies [406ab9b]
- Updated dependencies [4e16575]
- Updated dependencies [ea882ab]
- cojson@0.7.34
- cojson-transport-ws@0.7.34
## 0.0.59-neverthrow.6
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- cojson-transport-ws@0.7.34-neverthrow.8
## 0.0.59-neverthrow.5
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- cojson-transport-ws@0.7.34-neverthrow.7
## 0.0.59-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- cojson-transport-ws@0.7.34-neverthrow.4
## 0.0.59-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- cojson-transport-ws@0.7.34-neverthrow.3
## 0.0.59-neverthrow.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.34-neverthrow.2
## 0.0.59-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- cojson-transport-ws@0.7.34-neverthrow.1
## 0.0.59-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- cojson-transport-ws@0.7.34-neverthrow.0
## 0.0.58
### Patch Changes
- Updated dependencies [fdde8db]
- Updated dependencies [b297c93]
- Updated dependencies [07fe2b9]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson-transport-ws@0.7.33
- cojson@0.7.33
## 0.0.58-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- cojson-transport-ws@0.7.33-hotfixes.5
## 0.0.58-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- cojson-transport-ws@0.7.33-hotfixes.4
## 0.0.58-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.3
- cojson@0.7.33-hotfixes.3
## 0.0.58-hotfixes.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.2
## 0.0.58-hotfixes.1
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.1
## 0.0.58-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- cojson-transport-ws@0.7.33-hotfixes.0
## 0.0.57
### Patch Changes
- Updated dependencies
- Updated dependencies
- cojson-transport-ws@0.7.31
- cojson@0.7.31
## 0.0.56
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.30
## 0.0.55
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- cojson-transport-ws@0.7.29
## 0.0.54
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- cojson-transport-ws@0.7.28
## 0.0.53
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.27
## 0.0.52
### Patch Changes
- Updated dependencies
- cojson@0.7.26
- cojson-transport-ws@0.7.26
## 0.0.51
### Patch Changes
- Updated dependencies
- cojson@0.7.23
- cojson-transport-ws@0.7.23
## 0.0.50
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.22
## 0.0.49
### Patch Changes
- Updated dependencies
- cojson@0.7.18
- cojson-transport-ws@0.7.18
## 0.0.48
### Patch Changes
- Updated dependencies
- cojson@0.7.17
- cojson-transport-ws@0.7.17
## 0.0.47
### Patch Changes
- Updated dependencies
- cojson@0.6.7
- jazz-react@0.5.5
- jazz-react-auth-local@0.4.18
## 0.0.46
### Patch Changes
- Updated dependencies
- jazz-react@0.5.0
- jazz-react-auth-local@0.4.16

View File

@@ -1,4 +0,0 @@
FROM caddy:2.7.3-alpine
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
COPY ./dist /usr/share/caddy/

View File

@@ -1,42 +0,0 @@
# Jazz Chat Example
Live version: https://example-chat.jazz.tools
## Installing & running the example locally
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
Start by checking out `jazz`
```bash
git clone https://github.com/gardencmp/jazz.git
cd jazz/examples/chat
pnpm pack --pack-destination /tmp
mkdir -p ~/jazz-examples/chat # or any other directory
tar -xf /tmp/jazz-example-chat-* --strip-components 1 -C ~/jazz-examples/chat
cd ~/jazz-examples/chat
```
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
Install dependencies:
```bash
pnpm install
```
Start the dev server:
```bash
pnpm dev
```
## Questions / problems / feedback
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
## Configuration: sync server
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<WithJazz>` provider component in [./src/2_main.tsx](./src/2_main.tsx).

View File

@@ -1,17 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Inspector</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/app.tsx"></script>
</body>
</html>

View File

@@ -1,56 +0,0 @@
job "inspector$BRANCH_SUFFIX" {
region = "global"
datacenters = ["*"]
group "static" {
count = 4
network {
port "http" {
to = 80
}
}
constraint {
attribute = "${node.class}"
operator = "="
value = "mesh"
}
spread {
attribute = "${node.datacenter}"
weight = 100
}
constraint {
distinct_hosts = true
}
task "server" {
driver = "docker"
config {
image = "$DOCKER_TAG"
ports = ["http"]
auth = {
username = "$DOCKER_USER"
password = "$DOCKER_PASSWORD"
}
}
service {
tags = ["public"]
name = "inspector$BRANCH_SUFFIX"
port = "http"
provider = "consul"
}
resources {
cpu = 50 # MHz
memory = 50 # MB
}
}
}
}
# deploy bump 4

View File

@@ -1,48 +0,0 @@
{
"name": "jazz-inspector",
"private": true,
"version": "0.0.59",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"@types/qrcode": "^1.5.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"hash-slash": "workspace:*",
"cojson": "workspace:*",
"cojson-transport-ws": "workspace:*",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"react-use": "^17.4.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uniqolor": "^1.1.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.27",
"tailwindcss": "^3.3.3",
"typescript": "^5.0.2",
"vite": "^5.0.10"
}
}

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,4 +0,0 @@
import ReactDOM from "react-dom/client";
import App from "./viewer/new-app";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);

View File

@@ -1,92 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 24 9.8% 10%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 60 9.1% 97.8%;
--primary-foreground: 24 9.8% 10%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 24 5.7% 82.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
margin: 0;
padding: 0;
}
}
.animate-in {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateZ(400px) translateY(30px) scale(1.05);
opacity: 0.4;
}
to {
transform: translateZ(0) scale(1);
opacity: 1;
}
}

View File

@@ -1,18 +0,0 @@
export function LinkIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-3 h-3"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"
/>
</svg>
);
}

View File

@@ -1,42 +0,0 @@
import React from "react";
import { PageInfo } from "./types";
interface BreadcrumbsProps {
path: PageInfo[];
onBreadcrumbClick: (index: number) => void;
}
export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
path,
onBreadcrumbClick,
}) => {
return (
<div className="z-20 relative bg-indigo-400/10 backdrop-blur-sm rounded-lg inline-flex px-2 py-1 whitespace-pre transition-all items-center space-x-1 min-h-10">
<button
onClick={() => onBreadcrumbClick(-1)}
className="flex items-center justify-center p-1 rounded-sm hover:bg-indigo-500/10 transition-colors"
aria-label="Go to home"
>
<img src="jazz-logo.png" alt="Jazz Logo" className="size-5" />
</button>
{path.map((page, index) => {
return (
<span
key={index}
className="inline-block first:pl-1 last:pr-1"
>
{index === 0 ? null : (
<span className="text-indigo-500/30">{" / "}</span>
)}
<button
onClick={() => onBreadcrumbClick(index)}
className="text-indigo-700 hover:underline"
>
{index === 0 ? page.name || "Root" : page.name}
</button>
</span>
);
})}
</div>
);
};

View File

@@ -1,353 +0,0 @@
import {
CoID,
LocalNode,
RawBinaryCoStream,
RawCoStream,
RawCoValue,
} from "cojson";
import { JsonObject, JsonValue } from "cojson/src/jsonValue";
import { PageInfo } from "./types";
import { base64URLtoBytes } from "cojson/src/base64url";
import { useEffect, useState } from "react";
import { ArrowDownToLine } from "lucide-react";
import {
BinaryStreamItem,
BinaryStreamStart,
CoStreamItem,
} from "cojson/src/coValues/coStream";
import { AccountOrGroupPreview } from "./value-renderer";
// typeguard for BinaryStreamStart
function isBinaryStreamStart(item: unknown): item is BinaryStreamStart {
return (
typeof item === "object" &&
item !== null &&
"type" in item &&
item.type === "start"
);
}
function detectCoStreamType(value: RawCoStream | RawBinaryCoStream) {
const firstKey = Object.keys(value.items)[0];
if (!firstKey)
return {
type: "unknown",
};
const items = value.items[firstKey as never]?.map((v) => v.value);
if (!items)
return {
type: "unknown",
};
const firstItem = items[0];
if (!firstItem)
return {
type: "unknown",
};
// This is a binary stream
if (isBinaryStreamStart(firstItem)) {
return {
type: "binary",
items: items as BinaryStreamItem[],
};
} else {
return {
type: "coStream",
};
}
}
async function getBlobFromCoStream({
items,
onlyFirstChunk = false,
}: {
items: BinaryStreamItem[];
onlyFirstChunk?: boolean;
}) {
if (onlyFirstChunk && items.length > 1) {
items = items.slice(0, 2);
}
const chunks: Uint8Array[] = [];
const binary_U_prefixLength = 8;
let lastProgressUpdate = Date.now();
for (const item of items.slice(1)) {
if (item.type === "end") {
break;
}
if (item.type !== "chunk") {
console.error("Invalid binary stream chunk", item);
return undefined;
}
const chunk = base64URLtoBytes(item.chunk.slice(binary_U_prefixLength));
// totalLength += chunk.length;
chunks.push(chunk);
if (Date.now() - lastProgressUpdate > 100) {
lastProgressUpdate = Date.now();
}
}
const defaultMime = "mimeType" in items[0] ? items[0].mimeType : null;
const blob = new Blob(chunks, defaultMime ? { type: defaultMime } : {});
const mimeType =
defaultMime === "" ? await detectPDFMimeType(blob) : defaultMime;
return {
blob,
mimeType: mimeType as string,
unfinishedChunks: items.length > 1,
totalSize:
"totalSizeBytes" in items[0]
? (items[0].totalSizeBytes as number)
: undefined,
};
}
const detectPDFMimeType = async (blob: Blob): Promise<string> => {
const arrayBuffer = await blob.slice(0, 4).arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const header = uint8Array.reduce(
(acc, byte) => acc + String.fromCharCode(byte),
"",
);
if (header === "%PDF") {
return "application/pdf";
}
return "application/octet-stream";
};
const BinaryDownloadButton = ({
pdfBlob,
fileName = "document",
label,
mimeType,
}: {
pdfBlob: Blob;
mimeType?: string;
fileName?: string;
label: string;
}) => {
const downloadFile = () => {
const url = URL.createObjectURL(
new Blob([pdfBlob], mimeType ? { type: mimeType } : {}),
);
const link = document.createElement("a");
link.href = url;
link.download =
mimeType === "application/pdf" ? `${fileName}.pdf` : fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
return (
<button
className="flex items-center gap-2 px-2 py-1 text-gray-900 border border-gray-900/10 bg-clip-border shadow-sm transition-colors rounded bg-gray-50 text-sm"
onClick={downloadFile}
>
<ArrowDownToLine size={16} />
{label}
{/* Download {mimeType === "application/pdf" ? "PDF" : "File"} */}
</button>
);
};
const LabelContentPair = ({
label,
content,
}: {
label: string;
content: React.ReactNode;
}) => {
return (
<div className="flex flex-col gap-1.5 ">
<span className="uppercase text-xs font-medium text-gray-600 tracking-wide">
{label}
</span>
<span>{content}</span>
</div>
);
};
function RenderCoBinaryStream({
value,
items,
}: {
items: BinaryStreamItem[];
value: RawBinaryCoStream;
}) {
const [file, setFile] = useState<
| {
blob: Blob;
mimeType: string;
unfinishedChunks: boolean;
totalSize: number | undefined;
}
| undefined
| null
>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// load only the first chunk to get the mime type and size
getBlobFromCoStream({
items,
onlyFirstChunk: true,
})
.then((v) => {
if (v) {
setFile(v);
if (v.mimeType.includes("image")) {
// If it's an image, load the full blob
getBlobFromCoStream({
items,
}).then((s) => {
if (s) setFile(s);
});
}
}
})
.finally(() => setIsLoading(false));
}, [items]);
if (!isLoading && !file) return <div>No blob</div>;
if (isLoading) return <div>Loading...</div>;
if (!file) return <div>No blob</div>;
const { blob, mimeType } = file;
const sizeInKB = (file.totalSize || 0) / 1024;
return (
<div className="space-y-8 mt-4">
<div className="grid grid-cols-3 gap-2 max-w-3xl">
<LabelContentPair
label="Mime Type"
content={
<span className="font-mono bg-gray-100 rounded px-2 py-1 text-sm">
{mimeType || "No mime type"}
</span>
}
/>
<LabelContentPair
label="Size"
content={<span>{sizeInKB.toFixed(2)} KB</span>}
/>
<LabelContentPair
label="Download"
content={
<BinaryDownloadButton
fileName={value.id.toString()}
pdfBlob={blob}
mimeType={mimeType}
label={
mimeType === "application/pdf"
? "Download PDF"
: "Download File"
}
/>
}
/>
</div>
{mimeType === "image/png" || mimeType === "image/jpeg" ? (
<LabelContentPair
label="Preview"
content={
<div className="bg-gray-50 p-3 rounded-sm">
<RenderBlobImage blob={blob} />
</div>
}
/>
) : null}
</div>
);
}
function RenderCoStream({
value,
node,
}: {
value: RawCoStream;
node: LocalNode;
}) {
const streamPerUser = Object.keys(value.items);
const userCoIds = streamPerUser.map(
(stream) => stream.split("_session")[0],
);
return (
<div className="grid grid-cols-3 gap-2">
{userCoIds.map((id, idx) => (
<div
className="bg-gray-100 p-3 rounded-lg transition-colors overflow-hidden bg-white border hover:bg-gray-100/5 cursor-pointer shadow-sm"
key={id}
>
<AccountOrGroupPreview
coId={id as CoID<RawCoValue>}
node={node}
/>
{/* @ts-expect-error - TODO: fix types */}
{value.items[streamPerUser[idx]]?.map(
(item: CoStreamItem<JsonValue>) => (
<div>
{new Date(item.madeAt).toLocaleString()}{" "}
{JSON.stringify(item.value)}
</div>
),
)}
</div>
))}
</div>
);
}
export function CoStreamView({
value,
node,
}: {
data: JsonObject;
onNavigate: (pages: PageInfo[]) => void;
node: LocalNode;
value: RawCoStream;
}) {
// if (!value) return <div>No value</div>;
const streamType = detectCoStreamType(value);
if (streamType.type === "binary") {
if (streamType.items === undefined) {
return <div>No binary stream</div>;
}
return (
<RenderCoBinaryStream
value={value as RawBinaryCoStream}
items={streamType.items}
/>
);
}
if (streamType.type === "coStream") {
return <RenderCoStream value={value} node={node} />;
}
if (streamType.type === "unknown") return <div>Unknown stream type</div>;
return <div>Unknown stream type</div>;
}
function RenderBlobImage({ blob }: { blob: Blob }) {
const urlCreator = window.URL || window.webkitURL;
return <img src={urlCreator.createObjectURL(blob)} />;
}

View File

@@ -1,73 +0,0 @@
import { CoID, LocalNode, RawCoValue } from "cojson";
import { JsonObject } from "cojson/src/jsonValue";
import { CoMapPreview, ValueRenderer } from "./value-renderer";
import clsx from "clsx";
import { PageInfo, isCoId } from "./types";
import { ResolveIcon } from "./type-icon";
export function GridView({
data,
onNavigate,
node,
}: {
data: JsonObject;
onNavigate: (pages: PageInfo[]) => void;
node: LocalNode;
}) {
const entries = Object.entries(data);
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-2">
{entries.map(([key, child], childIndex) => (
<div
key={childIndex}
className={clsx(
"bg-gray-100 p-3 rounded-lg transition-colors overflow-hidden",
isCoId(child)
? "bg-white border hover:bg-gray-100/5 cursor-pointer shadow-sm"
: "bg-gray-50",
)}
onClick={() =>
isCoId(child) &&
onNavigate([
{ coId: child as CoID<RawCoValue>, name: key },
])
}
>
<h3 className="truncate">
{isCoId(child) ? (
<span className="font-medium flex justify-between">
{key}
<div className="px-2 py-1 text-xs bg-gray-100 rounded">
<ResolveIcon
coId={child as CoID<RawCoValue>}
node={node}
/>
</div>
</span>
) : (
<span>{key}</span>
)}
</h3>
<div className="mt-2 text-sm">
{isCoId(child) ? (
<CoMapPreview
coId={child as CoID<RawCoValue>}
node={node}
/>
) : (
<ValueRenderer
json={child}
onCoIDClick={(coId) => {
onNavigate([{ coId, name: key }]);
}}
compact
/>
)}
</div>
</div>
))}
</div>
);
}

View File

@@ -1,27 +0,0 @@
import { LocalNode } from "cojson";
import { Breadcrumbs } from "./breadcrumbs";
import { usePagePath } from "./use-page-path";
import { PageInfo } from "./types";
import { PageStack } from "./page-stack";
export default function CoJsonViewer({
defaultPath,
node,
}: {
defaultPath?: PageInfo[];
node: LocalNode;
}) {
const { path, addPages, goToIndex, goBack } = usePagePath(defaultPath);
return (
<div className="w-full h-screen bg-gray-100 p-4 overflow-hidden">
<Breadcrumbs path={path} onBreadcrumbClick={goToIndex} />
<PageStack
path={path}
node={node}
goBack={goBack}
addPages={addPages}
/>
</div>
);
}

View File

@@ -1,310 +0,0 @@
import React, { useState, useEffect } from "react";
import {
LocalNode,
CoID,
RawCoValue,
RawAccount,
AgentSecret,
AccountID,
cojsonInternals,
WasmCrypto,
} from "cojson";
import { createWebSocketPeer } from "cojson-transport-ws";
import { Trash2 } from "lucide-react";
import { Breadcrumbs } from "./breadcrumbs";
import { usePagePath } from "./use-page-path";
import { PageStack } from "./page-stack";
import { resolveCoValue, useResolvedCoValue } from "./use-resolve-covalue";
import clsx from "clsx";
interface Account {
id: CoID<RawAccount>;
secret: AgentSecret;
}
export default function CoJsonViewerApp() {
const [accounts, setAccounts] = useState<Account[]>(() => {
const storedAccounts = localStorage.getItem("inspectorAccounts");
return storedAccounts ? JSON.parse(storedAccounts) : [];
});
const [currentAccount, setCurrentAccount] = useState<Account | null>(() => {
const lastSelectedId = localStorage.getItem("lastSelectedAccountId");
if (lastSelectedId) {
const lastAccount = accounts.find(
(account) => account.id === lastSelectedId,
);
return lastAccount || null;
}
return null;
});
const [localNode, setLocalNode] = useState<LocalNode | null>(null);
const [coValueId, setCoValueId] = useState<CoID<RawCoValue> | "">("");
const { path, addPages, goToIndex, goBack, setPage } = usePagePath();
useEffect(() => {
localStorage.setItem("inspectorAccounts", JSON.stringify(accounts));
}, [accounts]);
useEffect(() => {
if (currentAccount) {
localStorage.setItem("lastSelectedAccountId", currentAccount.id);
} else {
localStorage.removeItem("lastSelectedAccountId");
}
}, [currentAccount]);
useEffect(() => {
if (!currentAccount) {
setLocalNode(null);
goToIndex(-1);
return;
}
WasmCrypto.create().then(async (crypto) => {
const wsPeer = createWebSocketPeer({
id: "mesh",
websocket: new WebSocket("wss://mesh.jazz.tools"),
role: "server",
});
const node = await LocalNode.withLoadedAccount({
accountID: currentAccount.id,
accountSecret: currentAccount.secret,
sessionID: cojsonInternals.newRandomSessionID(
currentAccount.id,
),
peersToLoadFrom: [wsPeer],
crypto,
migration: async () => {
console.log("Not running any migration in inspector");
},
});
setLocalNode(node);
});
}, [currentAccount, goToIndex]);
const addAccount = (id: AccountID, secret: AgentSecret) => {
const newAccount = { id, secret };
setAccounts([...accounts, newAccount]);
setCurrentAccount(newAccount);
};
const deleteCurrentAccount = () => {
if (currentAccount) {
const updatedAccounts = accounts.filter(
(account) => account.id !== currentAccount.id,
);
setAccounts(updatedAccounts);
setCurrentAccount(
updatedAccounts.length > 0 ? updatedAccounts[0] : null,
);
}
};
const handleCoValueIdSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (coValueId) {
setPage(coValueId);
}
};
return (
<div className="w-full h-screen bg-gray-100 p-4 overflow-hidden flex flex-col">
<div className="flex justify-between items-center mb-4">
<Breadcrumbs path={path} onBreadcrumbClick={goToIndex} />
<AccountSwitcher
accounts={accounts}
currentAccount={currentAccount}
setCurrentAccount={setCurrentAccount}
deleteCurrentAccount={deleteCurrentAccount}
localNode={localNode}
/>
</div>
<PageStack
path={path}
node={localNode}
goBack={goBack}
addPages={addPages}
>
{!currentAccount ? (
<AddAccountForm addAccount={addAccount} />
) : (
<form
onSubmit={handleCoValueIdSubmit}
aria-hidden={path.length !== 0}
className={clsx(
"flex flex-col justify-center items-center gap-2 h-full w-full mb-20 ",
"transition-all duration-150",
path.length > 0
? "opacity-0 -translate-y-2 scale-95"
: "opacity-100",
)}
>
<fieldset className="flex flex-col gap-2 text-sm">
<h2 className="text-3xl font-medium text-gray-950 text-center mb-4">
Jazz CoValue Inspector
</h2>
<input
className="border p-4 rounded-lg min-w-[21rem] font-mono"
placeholder="co_z1234567890abcdef123456789"
value={coValueId}
onChange={(e) =>
setCoValueId(
e.target.value as CoID<RawCoValue>,
)
}
/>
<button
type="submit"
className="bg-indigo-500 hover:bg-indigo-500/80 text-white px-4 py-2 rounded-md"
>
Inspect
</button>
<hr />
<button
type="button"
className="border inline-block px-2 py-1.5 text-black rounded"
onClick={() => {
setCoValueId(currentAccount.id);
setPage(currentAccount.id);
}}
>
Inspect My Account
</button>
</fieldset>
</form>
)}
</PageStack>
</div>
);
}
function AccountSwitcher({
accounts,
currentAccount,
setCurrentAccount,
deleteCurrentAccount,
localNode,
}: {
accounts: Account[];
currentAccount: Account | null;
setCurrentAccount: (account: Account | null) => void;
deleteCurrentAccount: () => void;
localNode: LocalNode | null;
}) {
return (
<div className="relative flex items-center gap-1">
<select
value={currentAccount?.id || "add-account"}
onChange={(e) => {
if (e.target.value === "add-account") {
setCurrentAccount(null);
} else {
const account = accounts.find(
(a) => a.id === e.target.value,
);
setCurrentAccount(account || null);
}
}}
className="p-2 px-4 bg-gray-100/50 border border-indigo-500/10 backdrop-blur-sm rounded-md text-indigo-700 appearance-none"
>
{accounts.map((account) => (
<option key={account.id} value={account.id}>
{localNode ? (
<AccountNameDisplay
accountId={account.id}
node={localNode}
/>
) : (
account.id
)}
</option>
))}
<option value="add-account">Add account</option>
</select>
{currentAccount && (
<button
onClick={deleteCurrentAccount}
className="p-3 rounded hover:bg-gray-200 transition-colors"
title="Delete Account"
>
<Trash2 size={16} className="text-gray-500" />
</button>
)}
</div>
);
}
function AddAccountForm({
addAccount,
}: {
addAccount: (id: AccountID, secret: AgentSecret) => void;
}) {
const [id, setId] = useState("");
const [secret, setSecret] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
addAccount(id as AccountID, secret as AgentSecret);
setId("");
setSecret("");
};
return (
<form
onSubmit={handleSubmit}
className="flex flex-col gap-2 max-w-md mx-auto h-full justify-center"
>
<h2 className="text-2xl font-medium text-gray-900 mb-3">
Add an Account to Inspect
</h2>
<input
className="border py-2 px-3 rounded-md"
placeholder="Account ID"
value={id}
onChange={(e) => setId(e.target.value)}
/>
<input
type="password"
className="border py-2 px-3 rounded-md"
placeholder="Account Secret"
value={secret}
onChange={(e) => setSecret(e.target.value)}
/>
<button
type="submit"
className="bg-indigo-500 text-white px-4 py-2 rounded-md"
>
Add Account
</button>
</form>
);
}
function AccountNameDisplay({
accountId,
node,
}: {
accountId: CoID<RawAccount>;
node: LocalNode;
}) {
const { snapshot } = useResolvedCoValue(accountId, node);
const [name, setName] = useState<string | null>(null);
useEffect(() => {
if (snapshot && typeof snapshot === "object" && "profile" in snapshot) {
const profileId = snapshot.profile as CoID<RawCoValue>;
resolveCoValue(profileId, node).then((profileResult) => {
if (
profileResult.snapshot &&
typeof profileResult.snapshot === "object" &&
"name" in profileResult.snapshot
) {
setName(profileResult.snapshot.name as string);
}
});
}
}, [snapshot, node]);
return name ? `${name} <${accountId}>` : accountId;
}

View File

@@ -1,55 +0,0 @@
import { Page } from "./page"; // Assuming you have a Page component
import { CoID, LocalNode, RawCoValue } from "cojson";
// Define the structure of a page in the path
interface PageInfo {
coId: CoID<RawCoValue>;
name?: string;
}
// Props for the PageStack component
interface PageStackProps {
path: PageInfo[];
node?: LocalNode | null;
goBack: () => void;
addPages: (pages: PageInfo[]) => void;
children?: React.ReactNode;
}
export function PageStack({
path,
node,
goBack,
addPages,
children,
}: PageStackProps) {
return (
<div className="relative mt-4 h-[calc(100vh-6rem)]">
{children && (
<div className="absolute inset-0 pb-20">{children}</div>
)}
{node &&
path.map((page, index) => (
<Page
key={`${page.coId}-${index}`}
coId={page.coId}
node={node}
name={page.name || page.coId}
onHeaderClick={goBack}
onNavigate={addPages}
isTopLevel={index === path.length - 1}
style={{
transform: `translateZ(${(index - path.length + 1) * 200}px) scale(${
1 - (path.length - index - 1) * 0.05
}) translateY(${-(index - path.length + 1) * -4}%)`,
opacity: 1 - (path.length - index - 1) * 0.05,
zIndex: index,
transitionProperty: "transform, opacity",
transitionDuration: "0.3s",
transitionTimingFunction: "ease-out",
}}
/>
))}
</div>
);
}

View File

@@ -1,154 +0,0 @@
import clsx from "clsx";
import { CoID, LocalNode, RawCoStream, RawCoValue } from "cojson";
import { useEffect, useState } from "react";
import { useResolvedCoValue } from "./use-resolve-covalue";
import { GridView } from "./grid-view";
import { PageInfo } from "./types";
import { TableView } from "./table-viewer";
import { TypeIcon } from "./type-icon";
import { CoStreamView } from "./co-stream-view";
import { AccountOrGroupPreview } from "./value-renderer";
type PageProps = {
coId: CoID<RawCoValue>;
node: LocalNode;
name: string;
onNavigate: (newPages: PageInfo[]) => void;
onHeaderClick?: () => void;
isTopLevel?: boolean;
style: React.CSSProperties;
};
export function Page({
coId,
node,
name,
onNavigate,
onHeaderClick,
style,
isTopLevel,
}: PageProps) {
const { value, snapshot, type, extendedType } = useResolvedCoValue(
coId,
node,
);
const [viewMode, setViewMode] = useState<"grid" | "table">("grid");
const supportsTableView = type === "colist" || extendedType === "record";
// Automatically switch to table view if the page is a CoMap record
useEffect(() => {
if (supportsTableView) {
setViewMode("table");
}
}, [supportsTableView]);
if (snapshot === "unavailable") {
return <div style={style}>Data unavailable</div>;
}
if (!snapshot) {
return <div style={style}></div>;
}
return (
<div
style={style}
className={clsx(
"absolute inset-0 border border-gray-900/5 bg-clip-padding bg-white rounded-xl shadow-lg p-6 animate-in",
)}
>
{!isTopLevel && (
<div
className="absolute inset-x-0 top-0 h-10"
aria-label="Back"
onClick={() => {
onHeaderClick?.();
}}
aria-hidden="true"
></div>
)}
<div className="flex justify-between items-center mb-4">
<div className="flex flex-col gap-2">
<h2 className="text-2xl font-bold flex items-start flex-col gap-1">
<span>
{name}
{typeof snapshot === "object" &&
"name" in snapshot ? (
<span className="text-gray-600 font-medium">
{" "}
{
(
snapshot as {
name: string;
}
).name
}
</span>
) : null}
</span>
</h2>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
{type && (
<TypeIcon
type={type}
extendedType={extendedType}
/>
)}
</span>
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
{coId}
</span>
</div>
</div>
{/* {supportsTableView && (
<button
onClick={toggleViewMode}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
>
{viewMode === "grid" ? "Table View" : "Grid View"}
</button>
)} */}
</div>
<div className="overflow-auto max-h-[calc(100%-4rem)]">
{type === "costream" ? (
<CoStreamView
data={snapshot}
onNavigate={onNavigate}
node={node}
value={value as RawCoStream}
/>
) : viewMode === "grid" ? (
<GridView
data={snapshot}
onNavigate={onNavigate}
node={node}
/>
) : (
<TableView
data={snapshot}
node={node}
onNavigate={onNavigate}
/>
)}
{/* --- */}
{extendedType !== "account" && extendedType !== "group" && (
<div className="text-xs text-gray-500 mt-4">
Owned by{" "}
<AccountOrGroupPreview
coId={value.group.id}
node={node}
showId
onClick={() => {
onNavigate([
{ coId: value.group.id, name: "owner" },
]);
}}
/>
</div>
)}
</div>
</div>
);
}

View File

@@ -1,142 +0,0 @@
import { CoID, LocalNode, RawCoValue } from "cojson";
import { JsonObject } from "cojson/src/jsonValue";
import { PageInfo } from "./types";
import { useMemo, useState } from "react";
import { ValueRenderer } from "./value-renderer";
import { LinkIcon } from "../link-icon";
import { useResolvedCoValues } from "./use-resolve-covalue";
export function TableView({
data,
node,
onNavigate,
}: {
data: JsonObject;
node: LocalNode;
onNavigate: (pages: PageInfo[]) => void;
}) {
const [visibleRowsCount, setVisibleRowsCount] = useState(10);
const [coIdArray, visibleRows] = useMemo(() => {
const coIdArray = Array.isArray(data)
? data
: Object.values(data).every(
(k) => typeof k === "string" && k.startsWith("co_"),
)
? Object.values(data).map((k) => k as CoID<RawCoValue>)
: [];
const visibleRows = coIdArray.slice(0, visibleRowsCount);
return [coIdArray, visibleRows];
}, [data, visibleRowsCount]);
const resolvedRows = useResolvedCoValues(visibleRows, node);
const hasMore = visibleRowsCount < coIdArray.length;
if (!coIdArray.length) {
return <div>No data to display</div>;
}
if (resolvedRows.length === 0) {
return <div>Loading...</div>;
}
const keys = Array.from(
new Set(
resolvedRows.flatMap((item) => Object.keys(item.snapshot || {})),
),
);
const loadMore = () => {
setVisibleRowsCount((prevVisibleRows) => prevVisibleRows + 10);
};
return (
<div>
<table className="min-w-full divide-y divide-gray-200">
<thead className="sticky top-0 border-b">
<tr>
{["", ...keys].map((key) => (
<th
key={key}
className="px-4 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 rounded"
>
{key}
</th>
))}
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{resolvedRows
.slice(0, visibleRowsCount)
.map((item, index) => (
<tr key={index}>
<td className="px-1 py-0">
<button
onClick={() =>
onNavigate([
{
coId: item.value!.id,
name: index.toString(),
},
])
}
className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 hover:text-blue-500 hover:bg-gray-100 rounded"
>
<LinkIcon />
</button>
</td>
{keys.map((key) => (
<td
key={key}
className="px-4 py-4 whitespace-nowrap text-sm text-gray-500"
>
<ValueRenderer
json={
(item.snapshot as JsonObject)[
key
]
}
onCoIDClick={(coId) => {
async function handleClick() {
onNavigate([
{
coId: item.value!
.id,
name: index.toString(),
},
{
coId: coId,
name: key,
},
]);
}
handleClick();
}}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
<div className="py-4 text-gray-500 flex items-center justify-between gap-2">
<span>
Showing {Math.min(visibleRowsCount, coIdArray.length)} of{" "}
{coIdArray.length}
</span>
{hasMore && (
<div className="text-center">
<button
onClick={loadMore}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Load More
</button>
</div>
)}
</div>
</div>
);
}

View File

@@ -1,47 +0,0 @@
import { CoID, LocalNode, RawCoValue } from "cojson";
import {
CoJsonType,
ExtendedCoJsonType,
useResolvedCoValue,
} from "./use-resolve-covalue";
export const TypeIcon = ({
type,
extendedType,
}: {
type: CoJsonType;
extendedType?: ExtendedCoJsonType;
}) => {
const iconMap: Record<ExtendedCoJsonType | CoJsonType, string> = {
record: "{} Record",
image: "🖼️ Image",
comap: "{} CoMap",
costream: "≋ CoStream",
colist: "☰ CoList",
account: "👤 Account",
group: "👥 Group",
};
const iconKey = extendedType || type;
const icon = iconMap[iconKey as keyof typeof iconMap];
return icon ? <span className="font-mono">{icon}</span> : null;
};
export const ResolveIcon = ({
coId,
node,
}: {
coId: CoID<RawCoValue>;
node: LocalNode;
}) => {
const { type, extendedType, snapshot } = useResolvedCoValue(coId, node);
if (snapshot === "unavailable" && !type) {
return <div className="text-gray-600 font-medium">Unavailable</div>;
}
if (!type) return <div className="whitespace-pre w-14 font-mono"> </div>;
return <TypeIcon type={type} extendedType={extendedType} />;
};

View File

@@ -1,9 +0,0 @@
import { CoID, RawCoValue } from "cojson";
export type PageInfo = {
coId: CoID<RawCoValue>;
name?: string;
};
export const isCoId = (coId: unknown): coId is CoID<RawCoValue> =>
typeof coId === "string" && coId.startsWith("co_");

View File

@@ -1,107 +0,0 @@
import { useState, useEffect, useCallback } from "react";
import { PageInfo } from "./types";
import { CoID, RawCoValue } from "cojson";
export function usePagePath(defaultPath?: PageInfo[]) {
const [path, setPath] = useState<PageInfo[]>(() => {
const hash = window.location.hash.slice(2); // Remove '#/'
if (hash) {
try {
return decodePathFromHash(hash);
} catch (e) {
console.error("Failed to parse hash:", e);
}
}
return defaultPath || [];
});
const updatePath = useCallback((newPath: PageInfo[]) => {
setPath(newPath);
const hash = encodePathToHash(newPath);
window.location.hash = `#/${hash}`;
}, []);
useEffect(() => {
const handleHashChange = () => {
const hash = window.location.hash.slice(2);
if (hash) {
try {
const newPath = decodePathFromHash(hash);
setPath(newPath);
} catch (e) {
console.error("Failed to parse hash:", e);
}
} else if (defaultPath) {
setPath(defaultPath);
}
};
window.addEventListener("hashchange", handleHashChange);
return () => window.removeEventListener("hashchange", handleHashChange);
}, [defaultPath]);
useEffect(() => {
if (
defaultPath &&
JSON.stringify(path) !== JSON.stringify(defaultPath)
) {
updatePath(defaultPath);
}
}, [defaultPath, path, updatePath]);
const addPages = useCallback(
(newPages: PageInfo[]) => {
updatePath([...path, ...newPages]);
},
[path, updatePath],
);
const goToIndex = useCallback(
(index: number) => {
updatePath(path.slice(0, index + 1));
},
[path, updatePath],
);
const setPage = useCallback(
(coId: CoID<RawCoValue>) => {
updatePath([{ coId, name: "Root" }]);
},
[updatePath],
);
const goBack = useCallback(() => {
if (path.length > 1) {
updatePath(path.slice(0, path.length - 1));
}
}, [path, updatePath]);
return {
path,
setPage,
addPages,
goToIndex,
goBack,
};
}
function encodePathToHash(path: PageInfo[]): string {
return path
.map((page) => {
if (page.name && page.name !== "Root") {
return `${page.coId}:${encodeURIComponent(page.name)}`;
}
return page.coId;
})
.join("/");
}
function decodePathFromHash(hash: string): PageInfo[] {
return hash.split("/").map((segment) => {
const [coId, encodedName] = segment.split(":");
return {
coId,
name: encodedName ? decodeURIComponent(encodedName) : undefined,
} as PageInfo;
});
}

View File

@@ -1,152 +0,0 @@
import { CoID, LocalNode, RawBinaryCoStream, RawCoValue } from "cojson";
import { useEffect, useState } from "react";
export type CoJsonType = "comap" | "costream" | "colist";
export type ExtendedCoJsonType = "image" | "record" | "account" | "group";
type JSON = string | number | boolean | null | JSON[] | { [key: string]: JSON };
type JSONObject = { [key: string]: JSON };
type ResolvedImageDefinition = {
originalSize: [number, number];
placeholderDataURL?: string;
[res: `${number}x${number}`]: RawBinaryCoStream["id"];
};
// Type guard for browser image
export const isBrowserImage = (
coValue: JSONObject,
): coValue is ResolvedImageDefinition => {
return "originalSize" in coValue && "placeholderDataURL" in coValue;
};
export type ResolvedGroup = {
readKey: string;
[key: string]: JSON;
};
export const isGroup = (coValue: JSONObject): coValue is ResolvedGroup => {
return "readKey" in coValue;
};
export type ResolvedAccount = {
profile: {
name: string;
};
[key: string]: JSON;
};
export const isAccount = (coValue: JSONObject): coValue is ResolvedAccount => {
return isGroup(coValue) && "profile" in coValue;
};
export async function resolveCoValue(
coValueId: CoID<RawCoValue>,
node: LocalNode,
): Promise<
| {
value: RawCoValue;
snapshot: JSONObject;
type: CoJsonType | null;
extendedType: ExtendedCoJsonType | undefined;
}
| {
value: undefined;
snapshot: "unavailable";
type: null;
extendedType: undefined;
}
> {
const value = await node.load(coValueId);
if (value === "unavailable") {
return {
value: undefined,
snapshot: "unavailable",
type: null,
extendedType: undefined,
};
}
const snapshot = value.toJSON() as JSONObject;
const type = value.type as CoJsonType;
// Determine extended type
let extendedType: ExtendedCoJsonType | undefined;
if (type === "comap") {
if (isBrowserImage(snapshot)) {
extendedType = "image";
} else if (isAccount(snapshot)) {
extendedType = "account";
} else if (isGroup(snapshot)) {
extendedType = "group";
} else {
// This check is a bit of a hack
// There might be a better way to do this
const children = Object.values(snapshot).slice(0, 10);
if (
children.every(
(c) => typeof c === "string" && c.startsWith("co_"),
) &&
children.length > 3
) {
extendedType = "record";
}
}
}
return {
value,
snapshot,
type,
extendedType,
};
}
export function useResolvedCoValue(
coValueId: CoID<RawCoValue>,
node: LocalNode,
) {
const [result, setResult] =
useState<Awaited<ReturnType<typeof resolveCoValue>>>();
useEffect(() => {
resolveCoValue(coValueId, node).then(setResult);
}, [coValueId, node]);
return (
result || {
value: undefined,
snapshot: undefined,
type: undefined,
extendedType: undefined,
}
);
}
export function useResolvedCoValues(
coValueIds: CoID<RawCoValue>[],
node: LocalNode,
) {
const [results, setResults] = useState<
Awaited<ReturnType<typeof resolveCoValue>>[]
>([]);
useEffect(() => {
console.log("RETECHING", coValueIds);
const fetchResults = async () => {
if (coValueIds.length === 0) return;
const resolvedValues = await Promise.all(
coValueIds.map((coValueId) => resolveCoValue(coValueId, node)),
);
console.log({ resolvedValues });
setResults(resolvedValues);
};
fetchResults();
}, [coValueIds, node]);
return results;
}

View File

@@ -1,248 +0,0 @@
import clsx from "clsx";
import { CoID, JsonValue, LocalNode, RawCoValue } from "cojson";
import { LinkIcon } from "../link-icon";
import {
isBrowserImage,
resolveCoValue,
useResolvedCoValue,
} from "./use-resolve-covalue";
import React, { useEffect, useState } from "react";
// Is there a chance we can pass the actual CoValue here?
export function ValueRenderer({
json,
compact,
onCoIDClick,
}: {
json: JsonValue | undefined;
compact?: boolean;
onCoIDClick?: (childNode: CoID<RawCoValue>) => void;
}) {
if (typeof json === "undefined" || json === undefined) {
return <span className="text-gray-400">undefined</span>;
}
if (json === null) {
return <span className="text-gray-400">null</span>;
}
if (typeof json === "string" && json.startsWith("co_")) {
return (
<span
className={clsx(
"inline-flex gap-1 items-center",
onCoIDClick &&
"text-blue-500 cursor-pointer hover:underline",
)}
onClick={() => {
onCoIDClick?.(json as CoID<RawCoValue>);
}}
>
{json}
{onCoIDClick && <LinkIcon />}
</span>
);
}
if (typeof json === "string") {
return (
<span className="text-green-900 font-mono">
{/* <span className="select-none opacity-70">{'"'}</span> */}
{json}
{/* <span className="select-none opacity-70">{'"'}</span> */}
</span>
);
}
if (typeof json === "number") {
return <span className="text-purple-500">{json}</span>;
}
if (typeof json === "boolean") {
return (
<span
className={clsx(
json
? "text-green-700 bg-green-700/5"
: "text-amber-700 bg-amber-500/5",
"font-mono",
"inline-block px-1 py-0.5 rounded",
)}
>
{json.toString()}
</span>
);
}
if (Array.isArray(json)) {
return (
<span title={JSON.stringify(json)}>
Array <span className="text-gray-500">({json.length})</span>
</span>
);
}
if (typeof json === "object") {
return (
<span
title={JSON.stringify(json, null, 2)}
className="inline-block max-w-64 truncate"
>
{compact ? (
<span>
Object{" "}
<span className="text-gray-500">
({Object.keys(json).length})
</span>
</span>
) : (
JSON.stringify(json, null, 2)
)}
</span>
);
}
return <span>{String(json)}</span>;
}
export const CoMapPreview = ({
coId,
node,
limit = 6,
}: {
coId: CoID<RawCoValue>;
node: LocalNode;
limit?: number;
}) => {
const { value, snapshot, type, extendedType } = useResolvedCoValue(
coId,
node,
);
if (!snapshot) {
return (
<div className="rounded bg-gray-100 animate-pulse whitespace-pre w-24">
{" "}
</div>
);
}
if (snapshot === "unavailable" && !value) {
return <div className="text-gray-500">Unavailable</div>;
}
if (extendedType === "image" && isBrowserImage(snapshot)) {
return (
<div>
<img
src={snapshot.placeholderDataURL}
className="size-8 border-2 border-white drop-shadow-md my-2"
/>
<span className="text-gray-500 text-sm">
{snapshot.originalSize[0]} x {snapshot.originalSize[1]}
</span>
{/* <CoMapPreview coId={value[]} node={node} /> */}
{/* <ProgressiveImg image={value}>
{({ src }) => <img src={src} className={clsx("w-full")} />}
</ProgressiveImg> */}
</div>
);
}
if (extendedType === "record") {
return (
<div>
Record{" "}
<span className="text-gray-500">
({Object.keys(snapshot).length})
</span>
</div>
);
}
if (type === "colist") {
return (
<div>
List{" "}
<span className="text-gray-500">
({(snapshot as unknown as []).length})
</span>
</div>
);
}
return (
<div className="text-sm flex flex-col gap-2 items-start">
<div className="grid grid-cols-[auto_1fr] gap-2">
{Object.entries(snapshot)
.slice(0, limit)
.map(([key, value]) => (
<React.Fragment key={key}>
<span className="font-medium">{key}: </span>
<span>
<ValueRenderer json={value} />
</span>
</React.Fragment>
))}
</div>
{Object.entries(snapshot).length > limit && (
<div className="text-left text-xs text-gray-500 mt-2">
{Object.entries(snapshot).length - limit} more
</div>
)}
</div>
);
};
export function AccountOrGroupPreview({
coId,
node,
showId = false,
onClick,
}: {
coId: CoID<RawCoValue>;
node: LocalNode;
showId?: boolean;
onClick?: (name?: string) => void;
}) {
const { snapshot, extendedType } = useResolvedCoValue(coId, node);
const [name, setName] = useState<string | null>(null);
useEffect(() => {
if (extendedType === "account") {
resolveCoValue(
(snapshot as unknown as { profile: CoID<RawCoValue> }).profile,
node,
).then(({ snapshot }) => {
if (
typeof snapshot === "object" &&
"name" in snapshot &&
typeof snapshot.name === "string"
) {
setName(snapshot.name);
}
});
}
}, [snapshot, node, extendedType]);
if (!snapshot) return <span>Loading...</span>;
if (extendedType !== "account" && extendedType !== "group") {
return <span>CoID is not an account or group</span>;
}
const displayName =
extendedType === "account" ? name || "Account" : "Group";
const displayText = showId ? `${displayName} (${coId})` : displayName;
const props = onClick
? {
onClick: () => onClick(displayName),
className: "text-blue-500 cursor-pointer hover:underline",
}
: {
className: "text-gray-500",
};
return <span {...props}>{displayText}</span>;
}

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,75 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}

View File

@@ -1,29 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,16 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
minify: false
}
})

View File

@@ -1,19 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier'
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,9 +0,0 @@
/** @type {import("prettier").Config} */
const config = {
trailingComma: "all",
tabWidth: 4,
semi: true,
singleQuote: false,
};
export default config;

View File

@@ -1,848 +0,0 @@
# jazz-example-pets
## 0.0.99
### Patch Changes
- jazz-react@0.7.34
- jazz-tools@0.7.34
- jazz-browser-media-images@0.7.34
## 0.0.99-neverthrow.6
### Patch Changes
- jazz-react@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
- jazz-browser-media-images@0.7.34-neverthrow.8
## 0.0.99-neverthrow.5
### Patch Changes
- jazz-react@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
- jazz-browser-media-images@0.7.34-neverthrow.7
## 0.0.99-neverthrow.4
### Patch Changes
- jazz-react@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
- jazz-browser-media-images@0.7.34-neverthrow.4
## 0.0.99-neverthrow.3
### Patch Changes
- jazz-react@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
- jazz-browser-media-images@0.7.34-neverthrow.3
## 0.0.99-neverthrow.2
### Patch Changes
- jazz-browser-media-images@0.7.34-neverthrow.2
- jazz-react@0.7.34-neverthrow.2
## 0.0.99-neverthrow.1
### Patch Changes
- jazz-react@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
- jazz-browser-media-images@0.7.34-neverthrow.1
## 0.0.99-neverthrow.0
### Patch Changes
- jazz-react@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
- jazz-browser-media-images@0.7.34-neverthrow.0
## 0.0.98
### Patch Changes
- jazz-react@0.7.33
- jazz-tools@0.7.33
- jazz-browser-media-images@0.7.33
## 0.0.98-hotfixes.5
### Patch Changes
- jazz-react@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
- jazz-browser-media-images@0.7.33-hotfixes.5
## 0.0.98-hotfixes.4
### Patch Changes
- jazz-react@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
- jazz-browser-media-images@0.7.33-hotfixes.4
## 0.0.98-hotfixes.3
### Patch Changes
- jazz-react@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
- jazz-browser-media-images@0.7.33-hotfixes.3
## 0.0.98-hotfixes.2
### Patch Changes
- jazz-browser-media-images@0.7.33-hotfixes.2
- jazz-react@0.7.33-hotfixes.2
## 0.0.98-hotfixes.1
### Patch Changes
- jazz-browser-media-images@0.7.33-hotfixes.1
- jazz-react@0.7.33-hotfixes.1
## 0.0.98-hotfixes.0
### Patch Changes
- jazz-react@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
- jazz-browser-media-images@0.7.33-hotfixes.0
## 0.0.97
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
- jazz-browser-media-images@0.7.32
- jazz-react@0.7.32
## 0.0.96
### Patch Changes
- jazz-react@0.7.31
- jazz-tools@0.7.31
- jazz-browser-media-images@0.7.31
## 0.0.95
### Patch Changes
- jazz-browser-media-images@0.7.30
- jazz-react@0.7.30
## 0.0.94
### Patch Changes
- jazz-react@0.7.29
- jazz-tools@0.7.29
- jazz-browser-media-images@0.7.29
## 0.0.93
### Patch Changes
- jazz-react@0.7.28
- jazz-tools@0.7.28
- jazz-browser-media-images@0.7.28
## 0.0.92
### Patch Changes
- jazz-browser-media-images@0.7.27
- jazz-react@0.7.27
## 0.0.91
### Patch Changes
- Updated dependencies
- jazz-react@0.7.26
- jazz-tools@0.7.26
- jazz-browser-media-images@0.7.26
## 0.0.90
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
- jazz-browser-media-images@0.7.25
- jazz-react@0.7.25
## 0.0.89
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.24
- jazz-browser-media-images@0.7.24
- jazz-react@0.7.24
## 0.0.88
### Patch Changes
- Updated dependencies
- jazz-react@0.7.23
- jazz-tools@0.7.23
- jazz-browser-media-images@0.7.23
## 0.0.87
### Patch Changes
- jazz-browser-media-images@0.7.22
- jazz-react@0.7.22
## 0.0.86
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.21
- jazz-browser-media-images@0.7.21
- jazz-react@0.7.21
## 0.0.85
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.20
- jazz-browser-media-images@0.7.20
- jazz-react@0.7.20
## 0.0.84
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.19
- jazz-browser-media-images@0.7.19
- jazz-react@0.7.19
## 0.0.83
### Patch Changes
- jazz-react@0.7.18
- jazz-tools@0.7.18
- jazz-browser-media-images@0.7.18
## 0.0.82
### Patch Changes
- jazz-react@0.7.17
- jazz-tools@0.7.17
- jazz-browser-media-images@0.7.17
## 0.0.81
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.16
- jazz-browser-media-images@0.7.16
- jazz-react@0.7.16
## 0.0.80
### Patch Changes
- Updated dependencies
- jazz-react@0.7.15
## 0.0.79
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.14
- jazz-react@0.7.14
- jazz-browser-media-images@0.7.14
## 0.0.78
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.13
- jazz-browser-media-images@0.7.13
- jazz-react@0.7.13
## 0.0.77
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.12
- jazz-browser-media-images@0.7.12
- jazz-react@0.7.12
## 0.0.76
### Patch Changes
- jazz-react@0.7.11
- jazz-tools@0.7.11
- jazz-browser-media-images@0.7.11
## 0.0.75
### Patch Changes
- jazz-react@0.7.10
- jazz-tools@0.7.10
- jazz-browser-media-images@0.7.10
## 0.0.74
### Patch Changes
- jazz-react@0.7.9
- jazz-tools@0.7.9
- jazz-browser-media-images@0.7.9
## 0.0.73
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.8
- jazz-browser-media-images@0.7.8
- jazz-react@0.7.8
## 0.0.72
### Patch Changes
- Updated dependencies [9fdc91c]
- jazz-react@0.7.7
## 0.0.71
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.6
- jazz-browser-media-images@0.7.6
- jazz-react@0.7.6
## 0.0.70
### Patch Changes
- Updated dependencies
- jazz-react@0.7.5
- jazz-browser-media-images@0.7.5
## 0.0.69
### Patch Changes
- Updated dependencies
- jazz-react@0.7.4
## 0.0.68
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.3
- jazz-browser-media-images@0.7.3
- jazz-react@0.7.3
## 0.0.67
### Patch Changes
- Updated dependencies
- jazz-react@0.7.2
## 0.0.66
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.1
- jazz-browser-media-images@0.7.1
- jazz-react@0.7.1
## 0.0.65
### Patch Changes
- Updated dependencies [8636319]
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [96c494f]
- Updated dependencies [59c18c3]
- Updated dependencies [19f52b7]
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [d8fe2b1]
- Updated dependencies [19004b4]
- Updated dependencies [a78f168]
- Updated dependencies [52675c9]
- Updated dependencies [129e2c1]
- Updated dependencies [6d49e9b]
- Updated dependencies [1cfa279]
- Updated dependencies [704af7d]
- Updated dependencies [e97f730]
- Updated dependencies [460478f]
- Updated dependencies [6b0418f]
- Updated dependencies [e299c3e]
- Updated dependencies [ed5643a]
- Updated dependencies [bde684f]
- Updated dependencies [c4151fc]
- Updated dependencies [63374cc]
- Updated dependencies [01ac646]
- Updated dependencies [a5e68a4]
- Updated dependencies [8636319]
- Updated dependencies [952982e]
- Updated dependencies [1a35307]
- Updated dependencies [5fa277c]
- Updated dependencies [60d5ca2]
- Updated dependencies [21771c4]
- Updated dependencies [77c2b56]
- Updated dependencies [63374cc]
- Updated dependencies [d2e03ff]
- Updated dependencies [354bdcd]
- Updated dependencies [ece35b3]
- Updated dependencies [60d5ca2]
- Updated dependencies [69ac514]
- Updated dependencies [f8a5c46]
- Updated dependencies [f0f6f1b]
- Updated dependencies [e5eed5b]
- Updated dependencies [1a44f87]
- Updated dependencies [627d895]
- Updated dependencies [1200aae]
- Updated dependencies [63374cc]
- Updated dependencies [ece35b3]
- Updated dependencies [38d4410]
- Updated dependencies [85d2b62]
- Updated dependencies [fd86c11]
- Updated dependencies [52675c9]
- jazz-tools@0.7.0
- jazz-browser-media-images@0.7.0
- jazz-react@0.7.0
## 0.0.65-alpha.42
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.42
- jazz-browser-media-images@0.7.0-alpha.40
- jazz-react@0.7.0-alpha.42
## 0.0.65-alpha.41
### Patch Changes
- jazz-tools@0.7.0-alpha.41
- jazz-browser-media-images@0.7.0-alpha.39
- jazz-react@0.7.0-alpha.41
## 0.0.65-alpha.40
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.40
## 0.0.65-alpha.39
### Patch Changes
- jazz-react@0.7.0-alpha.39
- jazz-tools@0.7.0-alpha.39
- jazz-browser-media-images@0.7.0-alpha.38
## 0.0.65-alpha.38
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.38
- jazz-react@0.7.0-alpha.38
- jazz-browser-media-images@0.7.0-alpha.37
## 0.0.65-alpha.37
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.37
- jazz-browser-media-images@0.7.0-alpha.36
- jazz-tools@0.7.0-alpha.37
## 0.0.65-alpha.36
### Patch Changes
- Updated dependencies [1a35307]
- Updated dependencies [1a35307]
- Updated dependencies [6b0418f]
- Updated dependencies [1a35307]
- jazz-tools@0.7.0-alpha.36
- jazz-react@0.7.0-alpha.36
- jazz-browser-media-images@0.7.0-alpha.35
## 0.0.65-alpha.35
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.35
- jazz-react@0.7.0-alpha.35
- jazz-browser-media-images@0.7.0-alpha.34
## 0.0.65-alpha.34
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.34
- jazz-browser-media-images@0.7.0-alpha.33
- jazz-react@0.7.0-alpha.34
## 0.0.65-alpha.33
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.33
## 0.0.65-alpha.32
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.32
- jazz-react@0.7.0-alpha.32
- jazz-browser-media-images@0.7.0-alpha.32
## 0.0.65-alpha.31
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.31
- jazz-browser-media-images@0.7.0-alpha.31
- jazz-react@0.7.0-alpha.31
## 0.0.65-alpha.30
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.30
- jazz-browser-media-images@0.7.0-alpha.30
- jazz-react@0.7.0-alpha.30
## 0.0.65-alpha.29
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.29
- jazz-browser-media-images@0.7.0-alpha.29
- jazz-react@0.7.0-alpha.29
## 0.0.65-alpha.28
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.28
- jazz-browser-media-images@0.7.0-alpha.28
- jazz-react@0.7.0-alpha.28
## 0.0.65-alpha.27
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.27
- jazz-browser-media-images@0.7.0-alpha.27
- jazz-react@0.7.0-alpha.27
## 0.0.65-alpha.26
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.26
- jazz-browser-media-images@0.7.0-alpha.26
- jazz-react@0.7.0-alpha.26
## 0.0.65-alpha.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.25
- jazz-browser-media-images@0.7.0-alpha.25
- jazz-react@0.7.0-alpha.25
## 0.0.65-alpha.24
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.24
- jazz-browser-media-images@0.7.0-alpha.24
- jazz-react@0.7.0-alpha.24
## 0.0.65-alpha.23
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.23
- jazz-browser-media-images@0.7.0-alpha.23
- jazz-react@0.7.0-alpha.23
## 0.0.65-alpha.22
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.22
- jazz-browser-media-images@0.7.0-alpha.22
- jazz-react@0.7.0-alpha.22
## 0.0.65-alpha.21
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.21
- jazz-tools@0.7.0-alpha.21
- jazz-browser-media-images@0.7.0-alpha.21
## 0.0.65-alpha.20
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-react@0.7.0-alpha.20
- jazz-tools@0.7.0-alpha.20
- jazz-browser-media-images@0.7.0-alpha.20
## 0.0.65-alpha.19
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.19
- jazz-browser-media-images@0.7.0-alpha.19
- jazz-react@0.7.0-alpha.19
## 0.0.65-alpha.18
### Patch Changes
- jazz-browser-media-images@0.7.0-alpha.18
- jazz-react@0.7.0-alpha.18
## 0.0.65-alpha.17
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.17
- jazz-browser-media-images@0.7.0-alpha.17
- jazz-react@0.7.0-alpha.17
## 0.0.65-alpha.16
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.16
- jazz-browser-media-images@0.7.0-alpha.16
- jazz-react@0.7.0-alpha.16
## 0.0.65-alpha.15
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.15
- jazz-browser-media-images@0.7.0-alpha.15
- jazz-react@0.7.0-alpha.15
## 0.0.65-alpha.14
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.14
- jazz-browser-media-images@0.7.0-alpha.14
- jazz-react@0.7.0-alpha.14
## 0.0.65-alpha.13
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.13
- jazz-browser-media-images@0.7.0-alpha.13
- jazz-react@0.7.0-alpha.13
## 0.0.65-alpha.12
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.12
- jazz-react@0.7.0-alpha.12
- jazz-tools@0.7.0-alpha.12
## 0.0.65-alpha.11
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.11
- jazz-react@0.7.0-alpha.11
- jazz-tools@0.7.0-alpha.11
## 0.0.65-alpha.10
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.10
- jazz-react@0.7.0-alpha.10
- jazz-tools@0.7.0-alpha.10
## 0.0.65-alpha.9
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.9
- jazz-react@0.7.0-alpha.9
- jazz-tools@0.7.0-alpha.9
## 0.0.65-alpha.8
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.8
- jazz-react@0.7.0-alpha.8
- jazz-tools@0.7.0-alpha.8
## 0.0.65-alpha.7
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.7
- jazz-react@0.7.0-alpha.7
- jazz-tools@0.7.0-alpha.7
## 0.0.65-alpha.6
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.6
- jazz-react@0.7.0-alpha.6
- jazz-tools@0.7.0-alpha.6
## 0.0.65-alpha.5
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.5
- jazz-react@0.7.0-alpha.5
- jazz-tools@0.7.0-alpha.5
## 0.0.65-alpha.4
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.4
- jazz-browser-media-images@0.7.0-alpha.4
- jazz-react@0.7.0-alpha.4
## 0.0.65-alpha.3
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.3
- jazz-browser-media-images@0.7.0-alpha.3
- jazz-react@0.7.0-alpha.3
## 0.0.65-alpha.2
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.2
- jazz-react@0.7.0-alpha.2
- jazz-tools@0.7.0-alpha.2
## 0.0.65-alpha.1
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.1
- jazz-react@0.7.0-alpha.1
- jazz-tools@0.7.0-alpha.1
## 0.0.65-alpha.0
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.7.0-alpha.0
- jazz-react@0.7.0-alpha.0
- jazz-tools@0.7.0-alpha.0
## 0.0.64
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.6.0
## 0.0.63
### Patch Changes
- Updated dependencies
- jazz-browser-media-images@0.5.0
- jazz-react@0.5.0
- jazz-react-auth-local@0.4.16

View File

@@ -1,4 +0,0 @@
FROM caddy:2.7.3-alpine
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
COPY ./dist /usr/share/caddy/

View File

@@ -1,42 +0,0 @@
# Jazz Rate-My-Pet List Example
Live version: https://example-pets.jazz.tools
## Installing & running the example locally
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
Start by checking out `jazz`
```bash
git clone https://github.com/gardencmp/jazz.git
cd jazz/examples/pets
pnpm pack --pack-destination /tmp
mkdir -p ~/jazz-examples/pets # or any other directory
tar -xf /tmp/jazz-example-pets-* --strip-components 1 -C ~/jazz-examples/pets
cd ~/jazz-examples/pets
```
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
Install dependencies:
```bash
pnpm install
```
Start the dev server:
```bash
pnpm dev
```
## Questions / problems / feedback
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
## Configuration: sync server
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/2_main.tsx](./src/2_main.tsx).

View File

@@ -1,16 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "stone",
"cssVariables": true
},
"aliases": {
"components": "@/basicComponents",
"utils": "@/basicComponents/lib/utils"
}
}

View File

@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Rate My Pet Example</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/2_main.tsx"></script>
</body>
</html>

View File

@@ -1,56 +0,0 @@
job "example-pets$BRANCH_SUFFIX" {
region = "global"
datacenters = ["*"]
group "static" {
count = 4
network {
port "http" {
to = 80
}
}
constraint {
attribute = "${node.class}"
operator = "="
value = "mesh"
}
spread {
attribute = "${node.datacenter}"
weight = 100
}
constraint {
distinct_hosts = true
}
task "server" {
driver = "docker"
config {
image = "$DOCKER_TAG"
ports = ["http"]
auth = {
username = "$DOCKER_USER"
password = "$DOCKER_PASSWORD"
}
}
service {
tags = ["public"]
name = "example-pets$BRANCH_SUFFIX"
port = "http"
provider = "consul"
}
resources {
cpu = 50 # MHz
memory = 50 # MB
}
}
}
}
# deploy bump 4

View File

@@ -1,52 +0,0 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.99",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"preview": "vite preview"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,mdx,json}": "prettier --write"
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"@types/qrcode": "^1.5.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uniqolor": "^1.1.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.27",
"tailwindcss": "^3.3.3",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,59 +0,0 @@
import {
Account,
CoList,
CoMap,
CoStream,
ImageDefinition,
Profile,
co,
} from "jazz-tools";
/** Walkthrough: Defining the data model with CoJSON
*
* Here, we define our main data model of TODO
*
* TODO
**/
export const ReactionTypes = [
"aww",
"love",
"haha",
"wow",
"tiny",
"chonkers",
] as const;
export type ReactionType = (typeof ReactionTypes)[number];
export class PetReactions extends CoStream.Of(co.json<ReactionType>()) {}
export class PetPost extends CoMap {
name = co.string;
image = co.ref(ImageDefinition);
reactions = co.ref(PetReactions);
}
export class ListOfPosts extends CoList.Of(co.ref(PetPost)) {}
export class PetAccountRoot extends CoMap {
posts = co.ref(ListOfPosts);
}
export class PetAccount extends Account {
profile = co.ref(Profile);
root = co.ref(PetAccountRoot);
migrate(this: PetAccount, creationProps?: { name: string }) {
super.migrate(creationProps);
if (!this._refs.root) {
this.root = PetAccountRoot.create(
{
posts: ListOfPosts.create([], { owner: this }),
},
{ owner: this },
);
}
}
}
/** Walkthrough: Continue with ./2_App.tsx */

View File

@@ -1,124 +0,0 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { Link, RouterProvider, createHashRouter } from "react-router-dom";
import "./index.css";
import { createJazzReactContext, PasskeyAuth } from "jazz-react";
import {
Button,
ThemeProvider,
TitleAndLogo,
} from "./basicComponents/index.ts";
import { PrettyAuthUI } from "./components/Auth.tsx";
import { NewPetPostForm } from "./3_NewPetPostForm.tsx";
import { RatePetPostUI } from "./4_RatePetPostUI.tsx";
import { PetAccount, PetPost } from "./1_schema.ts";
/** Walkthrough: The top-level provider `<WithJazz/>`
*
* This shows how to use the top-level provider `<WithJazz/>`,
* which provides the rest of the app with a `LocalNode` (used through `useJazz` later),
* based on `LocalAuth` that uses PassKeys (aka WebAuthn) to store a user's account secret
* - no backend needed. */
const appName = "Jazz Rate My Pet Example";
const auth = PasskeyAuth<PetAccount>({
appName,
Component: PrettyAuthUI,
accountSchema: PetAccount,
});
const Jazz = createJazzReactContext({
auth,
peer: "wss://mesh.jazz.tools/?key=you@example.com",
});
// eslint-disable-next-line react-refresh/only-export-components
export const { useAccount, useCoState, useAcceptInvite } = Jazz;
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ThemeProvider>
<TitleAndLogo name={appName} />
<div className="flex flex-col h-full items-center justify-start gap-10 pt-10 pb-10 px-5">
<Jazz.Provider loading={<div>Loading</div>}>
<App />
</Jazz.Provider>
</div>
</ThemeProvider>
</React.StrictMode>,
);
/** Walkthrough: Creating pet posts & routing in `<App/>`
*
* <App> is the main app component, handling client-side routing based
* on the CoValue ID (CoID) of our PetPost, stored in the URL hash
* - which can also contain invite links.
*/
export default function App() {
const { logOut } = useAccount();
const router = createHashRouter([
{
path: "/",
element: <PostOverview />,
},
{
path: "/new",
element: <NewPetPostForm />,
},
{
path: "/pet/:petPostId",
element: <RatePetPostUI />,
},
{
path: "/invite/*",
element: <p>Accepting invite...</p>,
},
]);
useAcceptInvite({
invitedObjectSchema: PetPost,
onAccept: (petPostID) => router.navigate("/pet/" + petPostID),
});
return (
<>
<RouterProvider router={router} />
<Button
onClick={() => router.navigate("/").then(logOut)}
variant="outline"
>
Log Out
</Button>
</>
);
}
export function PostOverview() {
const { me } = useAccount();
const myPosts = me.root?.posts;
return (
<>
{myPosts?.length ? (
<>
<h1>My posts</h1>
{myPosts.map(
(post) =>
post && (
<Link key={post.id} to={"/pet/" + post.id}>
{post.name}
</Link>
),
)}
</>
) : undefined}
<Link to="/new">New post</Link>
</>
);
}

View File

@@ -1,106 +0,0 @@
import { ChangeEvent, useCallback, useState } from "react";
import { useNavigate } from "react-router";
import { createImage } from "jazz-browser-media-images";
import { PetPost, PetReactions } from "./1_schema";
import { Input, Button } from "./basicComponents";
import { useAccount, useCoState } from "./2_main";
import { CoMap, Group, ID, ImageDefinition, co } from "jazz-tools";
import { ProgressiveImg } from "jazz-react";
/** Walkthrough: TODO
*/
class PartialPetPost extends CoMap {
name = co.string;
image = co.ref(ImageDefinition, { optional: true });
reactions = co.ref(PetReactions);
}
export function NewPetPostForm() {
const { me } = useAccount();
const navigate = useNavigate();
const [newPostId, setNewPostId] = useState<ID<PartialPetPost> | undefined>(
undefined,
);
const newPetPost = useCoState(PartialPetPost, newPostId);
const onChangeName = useCallback(
(name: string) => {
if (newPetPost) {
newPetPost.name = name;
} else {
const petPostGroup = Group.create({ owner: me });
const petPost = PartialPetPost.create(
{
name,
reactions: PetReactions.create([], { owner: me }),
},
{ owner: petPostGroup },
);
setNewPostId(petPost.id);
}
},
[me, newPetPost],
);
const onImageSelected = useCallback(
async (event: ChangeEvent<HTMLInputElement>) => {
if (!newPetPost || !event.target.files) return;
const image = await createImage(event.target.files[0], {
owner: newPetPost._owner,
});
newPetPost.image = image;
},
[newPetPost],
);
const onSubmit = useCallback(() => {
if (!newPetPost) return;
const myPosts = me.root?.posts;
if (!myPosts) {
throw new Error("No posts list found");
}
myPosts.push(newPetPost as PetPost);
navigate("/pet/" + newPetPost.id);
}, [me.root?.posts, newPetPost, navigate]);
return (
<div className="flex flex-col gap-10">
<p>Share your pet with friends!</p>
<Input
type="text"
placeholder="Pet Name"
className="text-3xl py-6"
onChange={(event) => onChangeName(event.target.value)}
value={newPetPost?.name || ""}
/>
{newPetPost?.image ? (
<ProgressiveImg image={newPetPost.image}>
{({ src }) => (
<img className="w-80 max-w-full rounded" src={src} />
)}
</ProgressiveImg>
) : (
<Input
type="file"
disabled={!newPetPost?.name}
onChange={onImageSelected}
/>
)}
{newPetPost?.name && newPetPost?.image && (
<Button onClick={onSubmit}>Submit Post</Button>
)}
</div>
);
}

View File

@@ -1,120 +0,0 @@
import { useParams } from "react-router";
import { PetPost, PetReactions, ReactionTypes } from "./1_schema";
import { ShareButton } from "./components/ShareButton";
import { Button, Skeleton } from "./basicComponents";
import uniqolor from "uniqolor";
import { ID } from "jazz-tools";
import { useCoState } from "./2_main";
import { ProgressiveImg } from "jazz-react";
/** Walkthrough: TODO
*/
const reactionEmojiMap: {
[reaction in (typeof ReactionTypes)[number]]: string;
} = {
aww: "😍",
love: "❤️",
haha: "😂",
wow: "😮",
tiny: "🐥",
chonkers: "🐘",
};
export function RatePetPostUI() {
const petPostID = useParams<{ petPostId: ID<PetPost> }>().petPostId;
const petPost = useCoState(PetPost, petPostID);
return (
<div className="flex flex-col gap-8">
<div className="flex justify-between">
<h1 className="text-3xl font-bold">{petPost?.name}</h1>
<ShareButton petPost={petPost} />
</div>
<ProgressiveImg image={petPost?.image}>
{({ src }) => (
<img className="w-80 max-w-full rounded" src={src} />
)}
</ProgressiveImg>
<div className="flex justify-between max-w-xs flex-wrap">
{ReactionTypes.map((reactionType) => (
<Button
key={reactionType}
variant={
petPost?.reactions?.byMe?.value === reactionType
? "default"
: "outline"
}
onClick={() => {
petPost?.reactions?.push(reactionType);
}}
title={`React with ${reactionType}`}
className="text-2xl px-2"
>
{reactionEmojiMap[reactionType]}
</Button>
))}
</div>
{petPost?._owner.myRole() === "admin" && petPost.reactions && (
<ReactionOverview petReactions={petPost.reactions} />
)}
</div>
);
}
function ReactionOverview({ petReactions }: { petReactions: PetReactions }) {
return (
<div>
<h2>Reactions</h2>
<div className="flex flex-col gap-1">
{ReactionTypes.map((reactionType) => {
const reactionsOfThisType = Object.values(
petReactions,
).filter((entry) => entry.value === reactionType);
if (reactionsOfThisType.length === 0) return null;
return (
<div
className="flex gap-2 items-center"
key={reactionType}
>
{reactionEmojiMap[reactionType]}{" "}
{reactionsOfThisType.map((reaction, idx) =>
reaction.by?.profile?.name ? (
<span
className="rounded-full py-0.5 px-2 text-xs"
style={uniqueColoring(reaction.by.id)}
key={reaction.by.id}
>
{reaction.by.profile.name}
</span>
) : (
<Skeleton
className="mt-1 w-[50px] h-[1em] rounded-full"
key={idx}
/>
),
)}
</div>
);
})}
</div>
</div>
);
}
function uniqueColoring(seed: string) {
const darkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
return {
color: uniqolor(seed, { lightness: darkMode ? 80 : 20 }).color,
background: uniqolor(seed, { lightness: darkMode ? 20 : 80 }).color,
};
}

View File

@@ -1,12 +0,0 @@
import { Toaster } from ".";
export function TitleAndLogo({ name }: { name: string }) {
return (
<>
<div className="flex items-center gap-2 justify-center mt-5">
<img src="jazz-logo.png" className="h-5" /> {name}
</div>
<Toaster />
</>
);
}

View File

@@ -1,7 +0,0 @@
export { Button } from "./ui/button";
export { Input } from "./ui/input";
export { Toaster } from "./ui/toaster";
export { useToast } from "./ui/use-toast";
export { Skeleton } from "./ui/skeleton";
export { TitleAndLogo } from "./TitleAndLogo";
export { ThemeProvider } from "./themeProvider";

View File

@@ -1,6 +0,0 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -1,73 +0,0 @@
import { createContext, useContext, useEffect, useState } from "react";
type ThemeProviderProps = {
children: React.ReactNode;
defaultTheme?: string;
storageKey?: string;
};
type ThemeProviderState = {
theme: string;
setTheme: (theme: string) => void;
};
const initialState = {
theme: "system",
setTheme: () => null,
};
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
export function ThemeProvider({
children,
defaultTheme = "system",
storageKey = "vite-ui-theme",
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState(
() => localStorage.getItem(storageKey) || defaultTheme,
);
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove("light", "dark");
if (theme === "system") {
const systemTheme = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches
? "dark"
: "light";
root.classList.add(systemTheme);
return;
}
root.classList.add(theme);
}, [theme]);
const value = {
theme,
setTheme: (theme: string) => {
localStorage.setItem(storageKey, theme);
setTheme(theme);
},
};
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
);
}
// eslint-disable-next-line react-refresh/only-export-components
export const useTheme = () => {
const context = useContext(ThemeProviderContext);
if (context === undefined)
throw new Error("useTheme must be used within a ThemeProvider");
return context;
};

View File

@@ -1,58 +0,0 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/basicComponents/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = "Button";
// eslint-disable-next-line react-refresh/only-export-components
export { Button, buttonVariants };

View File

@@ -1,25 +0,0 @@
import * as React from "react";
import { cn } from "@/basicComponents/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";
export { Input };

View File

@@ -1,15 +0,0 @@
import { cn } from "@/basicComponents/lib/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
}
export { Skeleton };

View File

@@ -1,127 +0,0 @@
import * as React from "react";
import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from "@/basicComponents/lib/utils";
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className,
)}
{...props}
/>
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
);
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className,
)}
{...props}
/>
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className,
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold", className)}
{...props}
/>
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
};

View File

@@ -1,41 +0,0 @@
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/basicComponents/ui/toast";
import { useToast } from "@/basicComponents/ui/use-toast";
export function Toaster() {
const { toasts } = useToast();
return (
<ToastProvider>
{toasts.map(function ({
id,
title,
description,
action,
...props
}) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>
{description}
</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
);
})}
<ToastViewport />
</ToastProvider>
);
}

View File

@@ -1,193 +0,0 @@
// Inspired by react-hot-toast library
import * as React from "react";
import type {
ToastActionElement,
ToastProps,
} from "@/basicComponents/ui/toast";
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & {
id: string;
title?: React.ReactNode;
description?: React.ReactNode;
action?: ToastActionElement;
};
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const;
let count = 0;
function genId() {
count = (count + 1) % Number.MAX_VALUE;
return count.toString();
}
type ActionType = typeof actionTypes;
type Action =
| {
type: ActionType["ADD_TOAST"];
toast: ToasterToast;
}
| {
type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast>;
}
| {
type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"];
}
| {
type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"];
};
interface State {
toasts: ToasterToast[];
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId);
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
});
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
};
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
};
case "DISMISS_TOAST": {
const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId);
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id);
});
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t,
),
};
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
};
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
};
}
};
const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] };
function dispatch(action: Action) {
memoryState = reducer(memoryState, action);
listeners.forEach((listener) => {
listener(memoryState);
});
}
type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) {
const id = genId();
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
});
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss();
},
},
});
return {
id: id,
dismiss,
update,
};
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => {
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1);
}
};
}, [state]);
return {
...state,
toast,
dismiss: (toastId?: string) =>
dispatch({ type: "DISMISS_TOAST", toastId }),
};
}
export { useToast, toast };

View File

@@ -1,48 +0,0 @@
import { useState } from "react";
import { PasskeyAuth } from "jazz-react";
import { Input, Button } from "../basicComponents";
export const PrettyAuthUI: PasskeyAuth.Component = ({
loading,
logIn,
signUp,
}) => {
const [username, setUsername] = useState<string>("");
return (
<div className="w-full h-full flex items-center justify-center p-5">
{loading ? (
<div>Loading...</div>
) : (
<div className="w-72 flex flex-col gap-4">
<form
className="w-72 flex flex-col gap-2"
onSubmit={(e) => {
e.preventDefault();
signUp(username);
}}
>
<Input
placeholder="Display name"
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="webauthn"
className="text-base"
/>
<Button asChild>
<Input
type="submit"
value="Sign Up as new account"
/>
</Button>
</form>
<Button onClick={logIn}>
Log In with existing account
</Button>
</div>
)}
</div>
);
};

View File

@@ -1,46 +0,0 @@
import { useState } from "react";
import { PetPost } from "../1_schema";
import { createInviteLink } from "jazz-react";
import QRCode from "qrcode";
import { useToast, Button } from "../basicComponents";
export function ShareButton({ petPost }: { petPost?: PetPost }) {
const [existingInviteLink, setExistingInviteLink] = useState<string>();
const { toast } = useToast();
return (
petPost?._owner.myRole() === "admin" && (
<Button
size="sm"
className="py-0"
disabled={!petPost}
variant="outline"
onClick={async () => {
let inviteLink = existingInviteLink;
if (petPost && !inviteLink) {
inviteLink = createInviteLink(petPost, "writer");
setExistingInviteLink(inviteLink);
}
if (inviteLink) {
const qr = await QRCode.toDataURL(inviteLink, {
errorCorrectionLevel: "L",
});
navigator.clipboard.writeText(inviteLink).then(() =>
toast({
title: "Copied invite link to clipboard!",
description: (
<img src={qr} className="w-20 h-20" />
),
}),
);
}
}}
>
Share
</Button>
)
);
}

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,76 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}

View File

@@ -1,29 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,16 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
minify: false
}
})

View File

@@ -5,7 +5,6 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier'
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',

View File

@@ -1,9 +0,0 @@
/** @type {import("prettier").Config} */
const config = {
trailingComma: "all",
tabWidth: 4,
semi: true,
singleQuote: false,
};
export default config;

View File

@@ -1,755 +0,0 @@
# jazz-example-todo
## 0.0.98
### Patch Changes
- jazz-react@0.7.34
- jazz-tools@0.7.34
## 0.0.98-neverthrow.6
### Patch Changes
- jazz-react@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.0.98-neverthrow.5
### Patch Changes
- jazz-react@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.0.98-neverthrow.4
### Patch Changes
- jazz-react@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.0.98-neverthrow.3
### Patch Changes
- jazz-react@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.0.98-neverthrow.2
### Patch Changes
- jazz-react@0.7.34-neverthrow.2
## 0.0.98-neverthrow.1
### Patch Changes
- jazz-react@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.0.98-neverthrow.0
### Patch Changes
- jazz-react@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.0.97
### Patch Changes
- jazz-react@0.7.33
- jazz-tools@0.7.33
## 0.0.97-hotfixes.5
### Patch Changes
- jazz-react@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.0.97-hotfixes.4
### Patch Changes
- jazz-react@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.0.97-hotfixes.3
### Patch Changes
- jazz-react@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.0.97-hotfixes.2
### Patch Changes
- jazz-react@0.7.33-hotfixes.2
## 0.0.97-hotfixes.1
### Patch Changes
- jazz-react@0.7.33-hotfixes.1
## 0.0.97-hotfixes.0
### Patch Changes
- jazz-react@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.0.96
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
- jazz-react@0.7.32
## 0.0.95
### Patch Changes
- jazz-react@0.7.31
- jazz-tools@0.7.31
## 0.0.94
### Patch Changes
- jazz-react@0.7.30
## 0.0.93
### Patch Changes
- jazz-react@0.7.29
- jazz-tools@0.7.29
## 0.0.92
### Patch Changes
- jazz-react@0.7.28
- jazz-tools@0.7.28
## 0.0.91
### Patch Changes
- jazz-react@0.7.27
## 0.0.90
### Patch Changes
- Updated dependencies
- jazz-react@0.7.26
- jazz-tools@0.7.26
## 0.0.89
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
- jazz-react@0.7.25
## 0.0.88
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.24
- jazz-react@0.7.24
## 0.0.87
### Patch Changes
- Updated dependencies
- jazz-react@0.7.23
- jazz-tools@0.7.23
## 0.0.86
### Patch Changes
- jazz-react@0.7.22
## 0.0.85
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.21
- jazz-react@0.7.21
## 0.0.84
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.20
- jazz-react@0.7.20
## 0.0.83
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.19
- jazz-react@0.7.19
## 0.0.82
### Patch Changes
- jazz-react@0.7.18
- jazz-tools@0.7.18
## 0.0.81
### Patch Changes
- jazz-react@0.7.17
- jazz-tools@0.7.17
## 0.0.80
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.16
- jazz-react@0.7.16
## 0.0.79
### Patch Changes
- Updated dependencies
- jazz-react@0.7.15
## 0.0.78
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.14
- jazz-react@0.7.14
## 0.0.77
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.13
- jazz-react@0.7.13
## 0.0.76
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.12
- jazz-react@0.7.12
## 0.0.75
### Patch Changes
- jazz-react@0.7.11
- jazz-tools@0.7.11
## 0.0.74
### Patch Changes
- jazz-react@0.7.10
- jazz-tools@0.7.10
## 0.0.73
### Patch Changes
- jazz-react@0.7.9
- jazz-tools@0.7.9
## 0.0.72
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.8
- jazz-react@0.7.8
## 0.0.71
### Patch Changes
- Updated dependencies [9fdc91c]
- jazz-react@0.7.7
## 0.0.70
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.6
- jazz-react@0.7.6
## 0.0.69
### Patch Changes
- Updated dependencies
- jazz-react@0.7.5
## 0.0.68
### Patch Changes
- Updated dependencies
- jazz-react@0.7.4
## 0.0.67
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.3
- jazz-react@0.7.3
## 0.0.66
### Patch Changes
- Updated dependencies
- jazz-react@0.7.2
## 0.0.65
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.1
- jazz-react@0.7.1
## 0.0.64
### Patch Changes
- Updated dependencies [8636319]
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [96c494f]
- Updated dependencies [59c18c3]
- Updated dependencies [19f52b7]
- Updated dependencies [8636319]
- Updated dependencies [1a35307]
- Updated dependencies [d8fe2b1]
- Updated dependencies [19004b4]
- Updated dependencies [a78f168]
- Updated dependencies [52675c9]
- Updated dependencies [129e2c1]
- Updated dependencies [6d49e9b]
- Updated dependencies [1cfa279]
- Updated dependencies [704af7d]
- Updated dependencies [e97f730]
- Updated dependencies [460478f]
- Updated dependencies [6b0418f]
- Updated dependencies [e299c3e]
- Updated dependencies [ed5643a]
- Updated dependencies [bde684f]
- Updated dependencies [c4151fc]
- Updated dependencies [63374cc]
- Updated dependencies [01ac646]
- Updated dependencies [a5e68a4]
- Updated dependencies [8636319]
- Updated dependencies [952982e]
- Updated dependencies [1a35307]
- Updated dependencies [5fa277c]
- Updated dependencies [60d5ca2]
- Updated dependencies [21771c4]
- Updated dependencies [77c2b56]
- Updated dependencies [63374cc]
- Updated dependencies [d2e03ff]
- Updated dependencies [354bdcd]
- Updated dependencies [ece35b3]
- Updated dependencies [60d5ca2]
- Updated dependencies [69ac514]
- Updated dependencies [f8a5c46]
- Updated dependencies [f0f6f1b]
- Updated dependencies [e5eed5b]
- Updated dependencies [1a44f87]
- Updated dependencies [627d895]
- Updated dependencies [1200aae]
- Updated dependencies [63374cc]
- Updated dependencies [ece35b3]
- Updated dependencies [38d4410]
- Updated dependencies [85d2b62]
- Updated dependencies [fd86c11]
- Updated dependencies [52675c9]
- jazz-tools@0.7.0
- jazz-react@0.7.0
## 0.0.64-alpha.42
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.42
- jazz-react@0.7.0-alpha.42
## 0.0.64-alpha.41
### Patch Changes
- jazz-tools@0.7.0-alpha.41
- jazz-react@0.7.0-alpha.41
## 0.0.64-alpha.40
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.40
## 0.0.64-alpha.39
### Patch Changes
- jazz-react@0.7.0-alpha.39
- jazz-tools@0.7.0-alpha.39
## 0.0.64-alpha.38
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.38
- jazz-react@0.7.0-alpha.38
## 0.0.64-alpha.37
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.37
- jazz-tools@0.7.0-alpha.37
## 0.0.64-alpha.36
### Patch Changes
- Updated dependencies [1a35307]
- Updated dependencies [1a35307]
- Updated dependencies [6b0418f]
- Updated dependencies [1a35307]
- jazz-tools@0.7.0-alpha.36
- jazz-react@0.7.0-alpha.36
## 0.0.64-alpha.35
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.35
- jazz-react@0.7.0-alpha.35
## 0.0.64-alpha.34
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.34
- jazz-react@0.7.0-alpha.34
## 0.0.64-alpha.33
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.33
## 0.0.64-alpha.32
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.32
- jazz-react@0.7.0-alpha.32
## 0.0.64-alpha.31
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.31
- jazz-react@0.7.0-alpha.31
## 0.0.64-alpha.30
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.30
- jazz-react@0.7.0-alpha.30
## 0.0.64-alpha.29
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.29
- jazz-react@0.7.0-alpha.29
## 0.0.64-alpha.28
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.28
- jazz-react@0.7.0-alpha.28
## 0.0.64-alpha.27
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.27
- jazz-react@0.7.0-alpha.27
## 0.0.64-alpha.26
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.26
- jazz-react@0.7.0-alpha.26
## 0.0.64-alpha.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.25
- jazz-react@0.7.0-alpha.25
## 0.0.64-alpha.24
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- jazz-tools@0.7.0-alpha.24
- jazz-react@0.7.0-alpha.24
## 0.0.64-alpha.23
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.23
- jazz-react@0.7.0-alpha.23
## 0.0.64-alpha.22
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.22
- jazz-react@0.7.0-alpha.22
## 0.0.64-alpha.21
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.21
- jazz-tools@0.7.0-alpha.21
## 0.0.64-alpha.20
### Patch Changes
- Updated dependencies
- Updated dependencies
- jazz-react@0.7.0-alpha.20
- jazz-tools@0.7.0-alpha.20
## 0.0.64-alpha.19
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.19
- jazz-react@0.7.0-alpha.19
## 0.0.64-alpha.18
### Patch Changes
- jazz-react@0.7.0-alpha.18
## 0.0.64-alpha.17
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.17
- jazz-react@0.7.0-alpha.17
## 0.0.64-alpha.16
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.16
- jazz-react@0.7.0-alpha.16
## 0.0.64-alpha.15
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.15
- jazz-react@0.7.0-alpha.15
## 0.0.64-alpha.14
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.14
- jazz-react@0.7.0-alpha.14
## 0.0.64-alpha.13
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.13
- jazz-react@0.7.0-alpha.13
## 0.0.64-alpha.12
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.12
- jazz-tools@0.7.0-alpha.12
## 0.0.64-alpha.11
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.11
- jazz-tools@0.7.0-alpha.11
## 0.0.64-alpha.10
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.10
- jazz-tools@0.7.0-alpha.10
## 0.0.64-alpha.9
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.9
- jazz-tools@0.7.0-alpha.9
## 0.0.64-alpha.8
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.8
- jazz-tools@0.7.0-alpha.8
## 0.0.64-alpha.7
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.7
- jazz-tools@0.7.0-alpha.7
## 0.0.64-alpha.6
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.6
- jazz-tools@0.7.0-alpha.6
## 0.0.64-alpha.5
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.5
- jazz-tools@0.7.0-alpha.5
## 0.0.64-alpha.4
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.4
- jazz-react@0.7.0-alpha.4
## 0.0.64-alpha.3
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.0-alpha.3
- jazz-react@0.7.0-alpha.3
## 0.0.64-alpha.2
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.2
- jazz-tools@0.7.0-alpha.2
## 0.0.64-alpha.1
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.1
- jazz-tools@0.7.0-alpha.1
## 0.0.64-alpha.0
### Patch Changes
- Updated dependencies
- jazz-react@0.7.0-alpha.0
- cojson@0.7.0-alpha.0
## 0.0.63
### Patch Changes
- Updated dependencies
- jazz-react@0.5.0
- jazz-react-auth-local@0.4.16

View File

@@ -1,4 +1,6 @@
FROM caddy:2.7.3-alpine
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
COPY ./dist /usr/share/caddy/
COPY ./dist /usr/share/caddy/
RUN caddy

View File

@@ -1,69 +1,27 @@
# Jazz Todo List Example
# React + TypeScript + Vite
Live version: https://example-todo.jazz.tools
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
## Installing & running the example locally
Currently, two official plugins are available:
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
Start by checking out `jazz`
```bash
git clone https://github.com/gardencmp/jazz.git
cd jazz/examples/todo
pnpm pack --pack-destination /tmp
mkdir -p ~/jazz-examples/todo # or any other directory
tar -xf /tmp/jazz-example-todo-* --strip-components 1 -C ~/jazz-examples/todo
cd ~/jazz-examples/todo
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
```
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
Install dependencies:
```bash
pnpm install
```
Start the dev server:
```bash
pnpm dev
```
## Structure
- [`src/basicComponents`](./src/basicComponents): simple components to build the UI, unrelated to Jazz (uses [shadcn/ui](https://ui.shadcn.com))
- [`src/components`](./src/components/): helper components that do contain Jazz-specific logic, but aren't very relevant to understand the basics of Jazz and CoJSON
- [`src/1_schema.ts`](./src/1_schema.ts),
[`src/2_main.tsx`](./src/2_main.tsx),
[`src/3_NewProjectForm.tsx`](./src/3_NewProjectForm.tsx),
[`src/4_ProjectTodoTable.tsx`](./src/4_ProjectTodoTable.tsx): the main files for this example, see the walkthrough below
## Walkthrough
### Main parts
1. Defining the data model with CoJSON: [`src/1_schema.ts`](./src/1_schema.ts)
2. The top-level provider `<WithJazz/>` and routing: [`src/2_main.tsx`](./src/2_main.tsx)
3. Creating a new todo project: [`src/3_NewProjectForm.tsx`](./src/3_NewProjectForm.tsx)
4. Reactively rendering a todo project as a table, adding and editing tasks: [`src/4_ProjectTodoTable.tsx`](./src/4_ProjectTodoTable.tsx)
### Helpers
- (not yet explained) Creating Invite Links/QR codes with `<InviteButton/>`: [`src/components/InviteButton.tsx`](./src/components/InviteButton.tsx)
This is the whole Todo List app!
## Questions / problems / feedback
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
## Configuration: sync server
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/2_main.tsx](./src/2_main.tsx).
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list

View File

@@ -10,7 +10,7 @@
"cssVariables": true
},
"aliases": {
"components": "@/basicComponents",
"utils": "@/basicComponents/lib/utils"
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@@ -2,12 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Todo List Example</title>
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/2_main.tsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,9 +1,9 @@
job "example-todo$BRANCH_SUFFIX" {
region = "global"
datacenters = ["*"]
region = "$REGION"
datacenters = ["$REGION"]
group "static" {
count = 4
// count = 3
network {
port "http" {
@@ -14,17 +14,13 @@ job "example-todo$BRANCH_SUFFIX" {
constraint {
attribute = "${node.class}"
operator = "="
value = "mesh"
value = "edge"
}
spread {
attribute = "${node.datacenter}"
weight = 100
}
constraint {
distinct_hosts = true
}
// spread {
// attribute = "${node.datacenter}"
// weight = 100
// }
task "server" {
driver = "docker"
@@ -41,7 +37,9 @@ job "example-todo$BRANCH_SUFFIX" {
service {
tags = ["public"]
name = "example-todo$BRANCH_SUFFIX"
meta {
public_name = "${BRANCH_SUBDOMAIN}example-todo"
}
port = "http"
provider = "consul"
}

View File

@@ -1,37 +1,26 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.98",
"version": "0.0.4",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier --write './src/**/*.{ts,tsx}'",
"preview": "vite preview"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.{js,jsx,mdx,json}": "prettier --write"
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"@types/qrcode": "^1.5.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"jazz-react": "^0.0.10",
"jazz-react-auth-local": "^0.0.7",
"lucide-react": "^0.265.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uniqolor": "^1.1.0"
"tailwindcss-animate": "^1.0.6"
},
"devDependencies": {
"@types/react": "^18.2.15",
@@ -41,7 +30,6 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"eslint": "^8.45.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.27",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show More