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
485 changed files with 22268 additions and 61487 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

@@ -7,64 +7,47 @@ on:
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
@@ -83,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,603 +0,0 @@
# jazz-example-chat
## 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://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 `<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.66",
"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,42 +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";
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(<Jazz.Provider><App/></Jazz.Provider>);

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,34 +0,0 @@
# jazz-example-chat
## 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,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 "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,49 +0,0 @@
{
"name": "jazz-inspector",
"private": true,
"version": "0.0.49",
"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:*",
"effect": "^3.5.2",
"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,309 +0,0 @@
import ReactDOM from "react-dom/client";
import {
RawAccount,
CoID,
RawCoValue,
SessionID,
LocalNode,
AgentSecret,
AccountID,
cojsonInternals,
WasmCrypto,
} from "cojson";
import { clsx } from "clsx";
import { AccountInfo, CoJsonTree, Tag } from "./cojson-tree";
import { useEffect, useState } from "react";
import { createWebSocketPeer } from "cojson-transport-ws";
import { Effect } from "effect";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
function App() {
const [accountID, setAccountID] = useState<CoID<RawAccount>>(
localStorage["inspectorAccountID"]
);
const [accountSecret, setAccountSecret] = useState<AgentSecret>(
localStorage["inspectorAccountSecret"]
);
const [coValueId, setCoValueId] = useState<CoID<RawCoValue>>(
window.location.hash.slice(2) as CoID<RawCoValue>
);
useEffect(() => {
window.addEventListener("hashchange", () => {
setCoValueId(window.location.hash.slice(2) as CoID<RawCoValue>);
});
});
const [localNode, setLocalNode] = useState<LocalNode>();
useEffect(() => {
if (!accountID || !accountSecret) return;
WasmCrypto.create().then(async (crypto) => {
const wsPeer = await Effect.runPromise(
createWebSocketPeer({
id: "mesh",
websocket: new WebSocket("wss://mesh.jazz.tools"),
role: "server",
})
);
const node = await LocalNode.withLoadedAccount({
accountID: accountID,
accountSecret: accountSecret,
sessionID: cojsonInternals.newRandomSessionID(accountID),
peersToLoadFrom: [wsPeer],
crypto,
migration: async () => {
console.log("Not running any migration in inspector");
},
});
setLocalNode(node);
});
}, [accountID, accountSecret]);
return (
<div className="flex flex-col items-center w-screen h-screen p-2 gap-2">
<div className="flex gap-2 items-center">
Account
<input
className="border p-2 rounded"
placeholder="Account ID"
value={accountID}
onChange={(e) => {
setAccountID(e.target.value as AccountID);
localStorage["inspectorAccountID"] = e.target.value;
}}
/>
<input
type="password"
className="border p-2 rounded"
placeholder="Account Secret"
value={accountSecret}
onChange={(e) => {
setAccountSecret(e.target.value as AgentSecret);
localStorage["inspectorAccountSecret"] = e.target.value;
}}
/>
{localNode ? (
<AccountInfo accountID={accountID} node={localNode} />
) : (
""
)}
</div>
<div className="flex gap-2 items-center">
CoValue ID
<input
className="border p-2 rounded min-w-[20rem]"
placeholder="CoValue ID"
value={coValueId}
onChange={(e) =>
setCoValueId(e.target.value as CoID<RawCoValue>)
}
/>
</div>
{coValueId && localNode ? (
<Inspect coValueId={coValueId} node={localNode} />
) : null}
</div>
);
}
// function ImageCoValue({ value }: { value: ImageDefinition["_shape"] }) {
// const keys = Object.keys(value);
// const keyIncludingRes = keys.find((key) => key.includes("x"));
// const idToResolve = keyIncludingRes
// ? value[keyIncludingRes as `${number}x${number}`]
// : null;
// if (!idToResolve) return <div>Can't find image</div>;
// const [blobURL, setBlobURL] = useState<string>();
// useEffect(() => {
// })
// return (
// <img
// src={image?.blobURL || value.placeholderDataURL}
// alt="placeholder"
// />
// );
// }
function Inspect({
coValueId,
node,
}: {
coValueId: CoID<RawCoValue>;
node: LocalNode;
}) {
const [coValue, setCoValue] = useState<RawCoValue | "unavailable">();
useEffect(() => {
return node.subscribe(coValueId, (coValue) => {
setCoValue(coValue);
});
}, [node, coValueId]);
if (coValue === "unavailable") {
return <div>Unavailable</div>;
}
const values = coValue?.toJSON() || {};
const isImage =
typeof values === "object" && "placeholderDataURL" in values;
const isGroup = coValue?.core.header.ruleset?.type === "group";
const entires = Object.entries(values as any) as [string, string][];
const onlyCoValues = entires.filter(([key]) => key.startsWith("co_"));
let title = "";
if (isImage) {
title = "Image";
} else if (isGroup) {
title = "Group";
}
return (
<div className="mb-auto">
<h1 className="text-xl font-bold mb-2">
Inspecting {title}{" "}
<span className="text-gray-500 text-sm">{coValueId}</span>
</h1>
{isGroup ? (
<p>
{onlyCoValues.length > 0 ? <h3>Permissions</h3> : ""}
<div className="flex gap-2 flex-col">
{onlyCoValues?.map(([key, value]) => (
<div className="flex gap-1 items-center">
<span className="bg-gray-200 text-xs px-2 py-0.5 rounded">
{value}
</span>
<AccountInfo
accountID={key as CoID<RawAccount>}
node={node}
/>
</div>
))}
</div>
</p>
) : (
<span className="">
Group{" "}
<Tag href={`#/${coValue?.group.id}`}>
{coValue?.group.id}
</Tag>
</span>
)}
{/* {isImage ? (
<div className="my-2">
<ImageCoValue value={values as any} />
</div>
) : null} */}
<pre className="max-w-[80vw] overflow-scroll text-sm mt-4">
<CoJsonTree coValueId={coValueId} node={node} />
</pre>
<h2 className="text-lg font-semibold mt-10 mb-4">Sessions</h2>
{coValue && <Sessions coValue={coValue} node={node} />}
</div>
);
}
function Sessions({ coValue, node }: { coValue: RawCoValue; node: LocalNode }) {
const validTx = coValue.core.getValidSortedTransactions();
return (
<div className="max-w-[80vw] border rounded">
{[...coValue.core.sessionLogs.entries()].map(
([sessionID, session]) => (
<div
key={sessionID}
className="mv-10 flex gap-2 border-b p-5 flex-wrap flex-col"
>
<div className="flex gap-2 flex-row">
<SessionInfo
sessionID={sessionID}
transactionCount={session.transactions.length}
node={node}
/>
</div>
<div className="flex gap-1 flex-wrap max-h-64 overflow-y-auto p-1 bg-gray-50 rounded">
{session.transactions.map((tx, txIdx) => {
const correspondingValidTx = validTx.find(
(validTx) =>
validTx.txID.sessionID === sessionID &&
validTx.txID.txIndex == txIdx
);
return (
<div
key={txIdx}
className={clsx(
"text-xs flex-1 p-2 border rounded min-w-36 max-w-40 overflow-scroll bg-white",
!correspondingValidTx &&
"bg-red-50 border-red-100"
)}
>
<div>
{new Date(
tx.madeAt
).toLocaleString()}
</div>
<div>{tx.privacy}</div>
<pre>
{correspondingValidTx
? JSON.stringify(
correspondingValidTx.changes,
undefined,
2
)
: "invalid/undecryptable"}
</pre>
</div>
);
})}
</div>
<div className="text-xs">
{session.lastHash} / {session.lastSignature}{" "}
</div>
</div>
)
)}
</div>
);
}
function SessionInfo({
sessionID,
transactionCount,
node,
}: {
sessionID: SessionID;
transactionCount: number;
node: LocalNode;
}) {
let Prefix = sessionID.startsWith("co_") ? (
<AccountInfo
accountID={sessionID.split("_session_")[0] as CoID<RawAccount>}
node={node}
/>
) : (
<pre className="text-xs">{sessionID.split("_session_")[0]}</pre>
);
return (
<div>
{Prefix}
<div>
<span className="text-xs">
Session {sessionID.split("_session_")[1]}
</span>
<span className="text-xs text-gray-600 font-medium">
{" "}
- {transactionCount} txs
</span>
</div>
</div>
);
}

View File

@@ -1,249 +0,0 @@
import clsx from "clsx";
import { AccountID, CoID, LocalNode, RawAccount, RawCoMap, RawCoValue } from "cojson";
import { useEffect, useState } from "react";
import { LinkIcon } from "./link-icon";
export function CoJsonTree({
coValueId,
node,
}: {
coValueId: CoID<RawCoValue>;
node: LocalNode;
}) {
const [coValue, setCoValue] = useState<RawCoValue | "unavailable">();
useEffect(() => {
return node.subscribe(coValueId, (value) => {
setCoValue(value);
});
});
if (coValue === "unavailable") {
return <div className="text-red-500">Unavailable</div>;
}
const values = coValue?.toJSON() || {};
return <RenderCoValueJSON json={values} node={node} />;
}
function RenderObject({
json,
node,
}: {
json: Record<string, any>;
node: LocalNode;
}) {
const [limit, setLimit] = useState(10);
const hasMore = Object.keys(json).length > limit;
const entries = Object.entries(json).slice(0, limit);
return (
<div className="flex gap-x-1 flex-col font-mono text-xs overflow-auto">
{"{"}
{entries.map(([key, value]) => {
return (
<RenderObjectValue
property={key}
value={value}
node={node}
/>
);
})}
{hasMore ? (
<div
className="text-gray-500 cursor-pointer"
onClick={() => setLimit((l) => l + 10)}
>
... {Object.keys(json).length - limit} more
</div>
) : null}
{"}"}
</div>
);
}
function RenderObjectValue({
property,
value,
node,
}: {
property: string;
value: any;
node: LocalNode;
}) {
const [shouldLoad, setShouldLoad] = useState(false);
const isCoValue =
typeof value === "string" ? value?.startsWith("co_") : false;
return (
<div className={clsx(`flex group`)}>
<div className="text-gray-500 flex items-start">
<div className="flex items-center">
<RenderCoValueJSON json={property} node={node} />:{" "}
</div>
</div>
{isCoValue ? (
<div className={clsx(shouldLoad && "pb-2")}>
<div className="flex items-center ">
<div onClick={() => setShouldLoad((s) => !s)}>
<div className="w-8 text-center text-gray-700 font-mono px-1 text-xs rounded hover:bg-gray-300 cursor-pointer">
{shouldLoad ? `-` : `...`}
</div>
</div>
<a
href={`#/${value}`}
className="ml-2 group-hover:block hidden"
>
<LinkIcon />
</a>
</div>
<span>
{shouldLoad ? (
<CoJsonTree coValueId={value} node={node} />
) : null}
</span>
</div>
) : (
<div className="">
<RenderCoValueJSON json={value} node={node} />
</div>
)}
</div>
);
}
function RenderCoValueArray({ json, node }: { json: any[]; node: LocalNode }) {
const [limit, setLimit] = useState(10);
const hasMore = json.length > limit;
const entries = json.slice(0, limit);
return (
<div className="flex gap-x-1 flex-col font-mono text-xs overflow-auto">
{entries.map((value, idx) => {
return (
<div key={idx} className="flex gap-x-1">
<RenderCoValueJSON json={value} node={node} />
</div>
);
})}
{hasMore ? (
<div
className="text-gray-500 cursor-pointer"
onClick={() => setLimit((l) => l + 10)}
>
... {json.length - limit} more
</div>
) : null}
</div>
);
}
function RenderCoValueJSON({
json,
node,
}: {
json:
| Record<string, any>
| any[]
| string
| null
| number
| boolean
| undefined;
node: LocalNode;
}) {
if (typeof json === "undefined") {
return <>"undefined"</>;
} else if (Array.isArray(json)) {
return (
<div className="">
<span className="text-gray-500">[</span>
<div className="ml-2">
<RenderCoValueArray json={json} node={node} />
</div>
<span className="text-gray-500">]</span>
</div>
);
} else if (
typeof json === "object" &&
json &&
Object.getPrototypeOf(json) === Object.prototype
) {
return <RenderObject json={json} node={node} />;
} else if (typeof json === "string") {
if (json?.startsWith("co_")) {
if (json.includes("_session_")) {
return (
<>
<AccountInfo accountID={json.split("_session_")[0] as AccountID} node={node}/>{" "}
(sess {json.split("_session_")[1]})
</>
);
} else {
return (
<>
<a className="underline" href={`#/${json}`}>
{'"'}
{json}
{'"'}
</a>
</>
);
}
} else {
return <div className="truncate max-w-64 ml-1">{json}</div>;
}
} else {
return <div className="truncate max-w-64">{JSON.stringify(json)}</div>;
}
}
export function AccountInfo({ accountID, node }: { accountID: CoID<RawAccount>, node: LocalNode }) {
const [name, setName] = useState<string | null>(null);
useEffect(() => {
(async () => {
const account = await node.load(accountID);
if (account === "unavailable") return;
const profileID = account?.get("profile");
if (profileID === undefined) return;
const profile = await node.load(profileID as CoID<RawCoMap>);
if (profile === "unavailable") return;
setName(profile?.get("name") as string);
})()
}, [accountID, node]);
return name ? (
<Tag href={`#/${accountID}`} title={accountID}><h1>{name}</h1></Tag>
) : (
<Tag href={`#/${accountID}`}>{accountID}</Tag>
);
}
export function Tag({
children,
href,
title
}: {
children: React.ReactNode;
href?: string;
title?: string;
}) {
if (href) {
return (
<a
href={href}
className="border text-xs px-2 py-0.5 rounded hover:underline"
title={title}
>
{children}
</a>
);
}
return (
<span className="border text-xs px-2 py-0.5 rounded">{children}</span>
);
}

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,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 +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,623 +0,0 @@
# jazz-example-pets
## 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.84",
"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,558 +0,0 @@
# jazz-example-todo
## 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.83",
"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

View File

@@ -1,55 +0,0 @@
import { Account, CoList, CoMap, Profile, co } from "jazz-tools";
/** Walkthrough: Defining the data model with CoJSON
*
* Here, we define our main data model of tasks, lists of tasks and projects
* using CoJSON's collaborative map and list types, CoMap & CoList.
*
* CoMap values and CoLists items can contain:
* - arbitrary immutable JSON
* - other CoValues
**/
/** An individual task which collaborators can tick or rename */
export class Task extends CoMap {
done = co.boolean;
text = co.string;
}
export class ListOfTasks extends CoList.Of(co.ref(Task)) {}
/** Our top level object: a project with a title, referencing a list of tasks */
export class TodoProject extends CoMap {
title = co.string;
tasks = co.ref(ListOfTasks);
}
export class ListOfProjects extends CoList.Of(co.ref(TodoProject)) {}
/** The account root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */
export class TodoAccountRoot extends CoMap {
projects = co.ref(ListOfProjects);
}
export class TodoAccount extends Account {
profile = co.ref(Profile);
root = co.ref(TodoAccountRoot);
/** The account migration is run on account creation and on every log-in.
* You can use it to set up the account root and any other initial CoValues you need.
*/
migrate(this: TodoAccount, creationProps?: { name: string }) {
super.migrate(creationProps);
if (!this._refs.root) {
this.root = TodoAccountRoot.create(
{
projects: ListOfProjects.create([], { owner: this }),
},
{ owner: this },
);
}
}
}
/** Walkthrough: Continue with ./2_main.tsx */

View File

@@ -1,132 +0,0 @@
import ReactDOM from "react-dom/client";
import {
RouterProvider,
createHashRouter,
useNavigate,
} 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 { NewProjectForm } from "./3_NewProjectForm.tsx";
import { ProjectTodoTable } from "./4_ProjectTodoTable.tsx";
import { TodoAccount, TodoProject } from "./1_schema.ts";
/**
* Walkthrough: The top-level provider `<Jazz.Provider/>`
*
* This shows how to use the top-level provider `<Jazz.Provider/>`,
* which provides the rest of the app with a controlled account (used through `useAccount` later).
* Here we use `PasskeyAuth`, which uses Passkeys (aka WebAuthn) to store a user's account secret
* - no backend needed.
*
* `<Jazz.Provider/>` also runs our account migration
*/
const appName = "Jazz Todo List Example";
const auth = PasskeyAuth<TodoAccount>({
appName,
Component: PrettyAuthUI,
accountSchema: TodoAccount,
});
const Jazz = createJazzReactContext<TodoAccount>({
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>
<App />
</Jazz.Provider>
</div>
</ThemeProvider>,
// </React.StrictMode>
);
/**
* Routing in `<App/>`
*
* <App> is the main app component, handling client-side routing based
* on the CoValue ID (CoID) of our TodoProject, stored in the URL hash
* - which can also contain invite links.
*/
export default function App() {
// logOut logs out the AuthProvider passed to `<Jazz.Provider/>` above.
const { logOut } = useAccount();
const router = createHashRouter([
{
path: "/",
element: <HomeScreen />,
},
{
path: "/project/:projectId",
element: <ProjectTodoTable />,
},
{
path: "/invite/*",
element: <p>Accepting invite...</p>,
},
]);
// `useAcceptInvite()` is a hook that accepts an invite link from the URL hash,
// and on success calls our callback where we navigate to the project that we were just invited to.
useAcceptInvite({
invitedObjectSchema: TodoProject,
forValueHint: "project",
onAccept: (projectID) => router.navigate("/project/" + projectID),
});
return (
<>
<RouterProvider router={router} />
<Button
onClick={() => router.navigate("/").then(logOut)}
variant="outline"
>
Log Out
</Button>
</>
);
}
function HomeScreen() {
const { me } = useAccount({
root: { projects: [{}] },
});
const navigate = useNavigate();
return (
<>
{me?.root.projects.length ? <h1>My Projects</h1> : null}
{me?.root.projects.map((project) => {
return (
<Button
key={project.id}
onClick={() => navigate("/project/" + project?.id)}
variant="ghost"
>
{project.title}
</Button>
);
})}
<NewProjectForm />
</>
);
}
/** Walkthrough: Continue with ./3_NewProjectForm.tsx */

View File

@@ -1,51 +0,0 @@
import { useCallback } from "react";
import { ListOfTasks, TodoProject } from "./1_schema";
import { SubmittableInput } from "./basicComponents";
import { useNavigate } from "react-router";
import { useAccount } from "./2_main";
import { Group } from "jazz-tools";
export function NewProjectForm() {
// `me` represents the current user account, which will determine
// access rights to CoValues. We get it from the top-level provider `<WithJazz/>`.
const { me } = useAccount();
const navigate = useNavigate();
const createProject = useCallback(
(title: string) => {
if (!title) return;
// To create a new todo project, we first create a `Group`,
// which is a scope for defining access rights (reader/writer/admin)
// of its members, which will apply to all CoValues owned by that group.
const projectGroup = Group.create({ owner: me });
// Then we create an empty todo project within that group
const project = TodoProject.create(
{
title,
tasks: ListOfTasks.create([], { owner: projectGroup }),
},
{ owner: projectGroup },
);
me.root?.projects?.push(project);
navigate("/project/" + project.id);
},
[me, navigate],
);
return (
<SubmittableInput
onSubmit={createProject}
label="Create New Project"
placeholder="New project title"
/>
);
}
/** Walkthrough: continue with ./4_ProjectTodoTable.tsx */

View File

@@ -1,177 +0,0 @@
import { useCallback } from "react";
import { TodoProject, Task } from "./1_schema";
import {
Checkbox,
SubmittableInput,
Skeleton,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "./basicComponents";
import { InviteButton } from "./components/InviteButton";
import uniqolor from "uniqolor";
import { useParams } from "react-router";
import { ID } from "jazz-tools";
import { useCoState } from "./2_main";
/** Walkthrough: Reactively rendering a todo project as a table,
* adding and editing tasks
*
* Here in `<TodoTable/>`, we use `useAutoSub()` for the first time,
* in this case to load the CoValue for our `TodoProject` as well as
* the `ListOfTasks` referenced in it.
*/
export function ProjectTodoTable() {
const projectId = useParams<{ projectId: ID<TodoProject> }>().projectId;
// `useAutoSub()` reactively subscribes to updates to a CoValue's
// content - whether we create edits locally, load persisted data, or receive
// sync updates from other devices or participants!
// It also recursively resolves and subsribes to all referenced CoValues.
const project = useCoState(TodoProject, projectId);
// `createTask` is similar to `createProject` we saw earlier, creating a new CoMap
// for a new task (in the same group as the project), and then
// adding that as an item to the project's list of tasks.
const createTask = useCallback(
(text: string) => {
if (!project?.tasks || !text) return;
const task = Task.create(
{
done: false,
text,
},
{ owner: project._owner },
);
// push will cause useCoState to rerender this component, both here and on other devices
project.tasks.push(task);
},
[project?.tasks, project?._owner],
);
return (
<div className="max-w-full w-4xl">
<div className="flex justify-between items-center gap-4 mb-4">
<h1>
{
// This is how we can access properties from the project query,
// accounting for the fact that note everything might be loaded yet
project?.title ? (
<>
{project.title}{" "}
<span className="text-sm">({project.id})</span>
</>
) : (
<Skeleton className="mt-1 w-[200px] h-[1em] rounded-full" />
)
}
</h1>
<InviteButton value={project} valueHint="project" />
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[40px]">Done</TableHead>
<TableHead>Task</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{project?.tasks?.map(
(task) => task && <TaskRow key={task.id} task={task} />,
)}
<NewTaskInputRow
createTask={createTask}
disabled={!project}
/>
</TableBody>
</Table>
</div>
);
}
export function TaskRow({ task }: { task: Task | undefined }) {
return (
<TableRow>
<TableCell>
<Checkbox
className="mt-1"
checked={task?.done}
onCheckedChange={(checked) => {
// Tick or untick the task
// Task is also immutable, but this will update all queries
// that include this task as a reference
if (task) task.done = !!checked;
}}
/>
</TableCell>
<TableCell>
<div className="flex flex-row justify-between items-center gap-2">
{task?.text ? (
<span className={task?.done ? "line-through" : ""}>
{task.text}
</span>
) : (
<Skeleton className="mt-1 w-[200px] h-[1em] rounded-full" />
)}
{
// Here we see for the first time how we can access edit history
// for a CoValue, and use it to display who created the task.
task?._edits.text?.by?.profile?.name ? (
<span
className="rounded-full py-0.5 px-2 text-xs"
style={uniqueColoring(task._edits.text.by.id)}
>
{task._edits.text.by.profile.name}
</span>
) : (
<Skeleton className="mt-1 w-[50px] h-[1em] rounded-full" />
)
}
</div>
</TableCell>
</TableRow>
);
}
/** Walkthrough: This is the end of the walkthrough so far! */
function NewTaskInputRow({
createTask,
disabled,
}: {
createTask: (text: string) => void;
disabled: boolean;
}) {
return (
<TableRow>
<TableCell>
<Checkbox className="mt-1" disabled />
</TableCell>
<TableCell>
<SubmittableInput
onSubmit={(taskText) => createTask(taskText)}
label="Add"
placeholder="New task"
disabled={disabled}
/>
</TableCell>
</TableRow>
);
}
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,
};
}

158
examples/todo/src/App.tsx Normal file
View File

@@ -0,0 +1,158 @@
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
import { CoMap, CoID } from "cojson";
import { useJazz, useTelepathicState } from "jazz-react";
type TaskContent = { done: boolean; text: string };
type Task = CoMap<TaskContent>;
type TodoListContent = { title: string; [taskId: CoID<Task>]: true };
type TodoList = CoMap<TodoListContent>;
function App() {
const [listId, setListId] = useState<CoID<TodoList>>(window.location.hash.slice(1) as CoID<TodoList>);
const { localNode } = useJazz();
const createList = () => {
const listTeam = localNode.createTeam();
const list = listTeam.createMap<TodoListContent>();
list.edit((list) => {
list.set("title", "My Todo List");
});
window.location.hash = list.id;
};
useEffect(() => {
const listener = () => {
setListId(window.location.hash.slice(1) as CoID<TodoList>);
}
window.addEventListener("hashchange", listener);
return () => {
window.removeEventListener("hashchange", listener);
}
}, [])
return (
<div className="flex flex-col h-full items-center justify-start gap-10 pt-10 md:pt-[30vh] pb-10">
{listId && <TodoList listId={listId} />}
<Button onClick={createList}>Create New List</Button>
</div>
);
}
export function TodoList({ listId }: { listId: CoID<TodoList> }) {
const list = useTelepathicState(listId);
const createTodo = (text: string) => {
if (!list) return;
let task = list.coValue.getTeam().createMap<TaskContent>();
task = task.edit((task) => {
task.set("text", text);
task.set("done", false);
});
console.log("Created task", task.id, task.toJSON());
const listAfter = list.edit((list) => {
list.set(task.id, true);
});
console.log("Updated list", listAfter.toJSON());
};
return (
<div className="max-w-full w-4xl">
<h1>
{list?.get("title")} ({list?.id})
</h1>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[40px]">Done</TableHead>
<TableHead>Task</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{list &&
list
.keys()
.filter((key): key is CoID<Task> =>
key.startsWith("co_")
)
.map((taskId) => (
<TodoRow key={taskId} taskId={taskId} />
))}
<TableRow key="new">
<TableCell>
<Checkbox className="mt-1" disabled />
</TableCell>
<TableCell>
<form
className="flex flex-row items-center gap-5"
onSubmit={(e) => {
e.preventDefault();
const textEl =
e.currentTarget.elements.namedItem(
"text"
) as HTMLInputElement;
createTodo(textEl.value);
textEl.value = "";
}}
>
<Input
className="-ml-3 -my-2"
name="text"
placeholder="Add todo"
autoComplete="off"
/>
<Button asChild type="submit">
<Input type="submit" value="Add" />
</Button>
</form>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
);
}
function TodoRow({ taskId }: { taskId: CoID<Task> }) {
const task = useTelepathicState(taskId);
return (
<TableRow>
<TableCell>
<Checkbox
className="mt-1"
checked={task?.get("done")}
onCheckedChange={(checked) => {
task?.edit((task) => {
task.set("done", !!checked);
});
}}
/>
</TableCell>
<TableCell className={task?.get("done") ? "line-through" : ""}>
{task?.get("text")}
</TableCell>
</TableRow>
);
}
export default App;

View File

@@ -0,0 +1,30 @@
import { Input } from "./components/ui/input.tsx";
import { Button } from "./components/ui/button.tsx";
import { AuthComponent } from "jazz-react";
import { useLocalAuth } from "jazz-react-auth-local";
export const LocalAuth: AuthComponent = ({ onCredential }) => {
const { displayName, setDisplayName, signIn, signUp } = useLocalAuth(onCredential);
return (<div className="w-full h-full flex items-center justify-center">
<div className="w-72 flex flex-col gap-4">
<form
className="w-72 flex flex-col gap-2"
onSubmit={(e) => {
e.preventDefault();
signUp();
}}
>
<Input
placeholder="Display name"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
autoComplete="webauthn" />
<Button asChild>
<Input type="submit" value="Sign Up as new account" />
</Button>
</form>
<Button onClick={signIn}>Log In with existing account</Button>
</div>
</div>);
};

View File

@@ -1,43 +0,0 @@
import { Input } from "@/basicComponents/ui/input";
import { Button } from "@/basicComponents/ui/button";
export function SubmittableInput({
onSubmit,
label,
placeholder,
disabled,
}: {
onSubmit: (text: string) => void;
label: string;
placeholder: string;
disabled?: boolean;
}) {
return (
<form
className="flex flex-row items-center gap-3"
onSubmit={(e) => {
e.preventDefault();
const textEl = e.currentTarget.elements.namedItem(
"text",
) as HTMLInputElement;
onSubmit(textEl.value);
textEl.value = "";
}}
>
<Input
className="-ml-3 -my-2 flex-grow flex-3 text-base"
name="text"
placeholder={placeholder}
autoComplete="off"
disabled={disabled}
/>
<Button
asChild
type="submit"
className="flex-shrink flex-1 cursor-pointer"
>
<Input type="submit" value={label} disabled={disabled} />
</Button>
</form>
);
}

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,17 +0,0 @@
export { Button } from "./ui/button";
export { Checkbox } from "./ui/checkbox";
export { Input } from "./ui/input";
export { Skeleton } from "./ui/skeleton";
export { Toaster } from "./ui/toaster";
export { useToast } from "./ui/use-toast";
export { SubmittableInput } from "./SubmittableInput";
export { TitleAndLogo } from "./TitleAndLogo";
export { ThemeProvider } from "./themeProvider";
export {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "./ui/table";

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 };

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