Compare commits

..

50 Commits

Author SHA1 Message Date
Benjamin S. Leveritt
a4e342a59b Adds CreateImg config, annotates other methods 2025-03-17 09:32:38 +00:00
Benjamin S. Leveritt
73b71df524 Adds React Native docs 2025-03-17 09:32:38 +00:00
Benjamin S. Leveritt
fcaa5dddda Add Creating Images section to vanilla 2025-03-17 09:32:37 +00:00
Benjamin S. Leveritt
c27fadc4e0 Adds comment about knowness 2025-03-17 09:32:37 +00:00
Benjamin S. Leveritt
56af3c7412 Reword to synced 2025-03-17 09:32:37 +00:00
Benjamin S. Leveritt
5e5ef10675 Tweak presentation of FileStream 2025-03-17 09:32:37 +00:00
Benjamin S. Leveritt
2efc55f9bf Switches sections 2025-03-17 09:32:37 +00:00
Benjamin S. Leveritt
7101f10573 Add link to Writing to FileStreams 2025-03-17 09:32:37 +00:00
Benjamin S. Leveritt
3849392272 Add links to Vanilla docs 2025-03-17 09:32:37 +00:00
Benjamin S. Leveritt
ce1fc720c1 Add React docs 2025-03-17 09:32:36 +00:00
Benjamin S. Leveritt
4eb634175d Adds revokeObjectURL references and best practices 2025-03-17 09:32:36 +00:00
Benjamin S. Leveritt
adc6343531 Fix note section 2025-03-17 09:32:36 +00:00
Benjamin S. Leveritt
c745e099fe Adds example for fallback behaviour 2025-03-17 09:32:36 +00:00
Benjamin S. Leveritt
970c9f5c6c Adds imageDef docs 2025-03-17 09:32:36 +00:00
Benjamin S. Leveritt
4e3986ae98 Removes mentions of images, pointing to ImageDef 2025-03-17 09:32:36 +00:00
Benjamin S. Leveritt
6c691bf641 Adds tests for imageDef 2025-03-17 09:32:36 +00:00
Benjamin S. Leveritt
d03ba7fdd0 Adds CodeGroups 2025-03-17 09:32:35 +00:00
Benjamin S. Leveritt
35cb7d8988 Add FileStream progress test 2025-03-17 09:32:35 +00:00
Benjamin S. Leveritt
0e40a9daab Adds FileStream doc draft 2025-03-17 09:32:35 +00:00
Trisha Lim
bd1996c458 make dev generate app.js instead of jazz-inspector.js 2025-03-17 10:25:19 +07:00
Trisha Lim
373ebec04c add basic json viewer 2025-03-17 10:25:19 +07:00
Trisha Lim
7fedbeb8e4 add covalue search to nav 2025-03-17 10:25:19 +07:00
Trisha Lim
4bfc23091c bare minimum dark styles 2025-03-17 10:25:19 +07:00
Trisha Lim
6e5ea6f19c add inspector to chat app 2025-03-17 10:25:19 +07:00
Trisha Lim
bcd67cb23f replace indigo with blue 2025-03-17 10:25:19 +07:00
Trisha Lim
27781ed778 remove javascript hover styles 2025-03-17 10:25:19 +07:00
Trisha Lim
eb097ecdb1 match colors to jazz brand 2025-03-17 10:25:19 +07:00
Trisha Lim
cdae2603ba add button label 2025-03-17 10:25:19 +07:00
Trisha Lim
2b3490cce2 rewrite styles to tailwind 2025-03-17 10:25:19 +07:00
Trisha Lim
4baba65cdd install twind 2025-03-17 10:25:19 +07:00
Guido D'Orsi
c6ef96d642 Merge pull request #1523 from garden-co/jazz-744-maintain-docs-side-nav-scroll-state-on-navigation
Maintain docs side nav scroll state on navigation
2025-03-14 17:16:11 +01:00
Guido D'Orsi
385ce9ba37 Merge pull request #1664 from garden-co/changeset-release/main
Version Packages
2025-03-14 16:10:23 +01:00
github-actions[bot]
d128490da5 Version Packages 2025-03-14 15:09:56 +00:00
Guido D'Orsi
60f5b3ffeb fix: downgrade the WasmCrypto initialization error logging to a warning 2025-03-14 16:06:55 +01:00
Guido D'Orsi
7d496b03e4 Merge pull request #1638 from garden-co/changeset-release/main
Version Packages
2025-03-14 15:41:18 +01:00
github-actions[bot]
261c9aa51d Version Packages 2025-03-14 14:31:36 +00:00
Guido D'Orsi
5ac5d732a9 Merge pull request #1650 from garden-co/purejs-fallback
fix: use PureJSCrypto as fallback when WasmCrypto fails to initialize
2025-03-14 15:29:23 +01:00
Guido D'Orsi
f2e9bc9f92 Merge pull request #1660 from garden-co/feat/inspector-position
add changeset
2025-03-14 15:21:44 +01:00
Guido D'Orsi
6d4b8ecfde Merge pull request #1655 from garden-co/fix/e2e-rn-test
test: fix the mobile e2e tests
2025-03-14 15:20:09 +01:00
Guido D'Orsi
2da366e1cd fix: comment out the re-login stuff 2025-03-14 10:17:57 +01:00
Trisha Lim
e902405c93 add changeset 2025-03-14 11:17:02 +07:00
Trisha Lim
ecbf6d0248 add inspector to version history example 2025-03-14 11:15:27 +07:00
Guido D'Orsi
d0fd66df00 test: fix the mobile e2e tests 2025-03-13 11:05:01 +01:00
Guido D'Orsi
7f036c170e fix: use PureJSCrypto as fallback when WasmCrypto fails to initialize 2025-03-12 16:21:21 +01:00
Guido D'Orsi
ce612394dc chore: move the cloudflare test to use WASM 2025-03-12 16:19:17 +01:00
Trisha Lim
65b2947c18 clean up 2025-02-26 21:14:59 +07:00
Trisha Lim
f9147dae85 TOC fixes 2025-02-26 21:12:56 +07:00
Trisha Lim
21309953ee fix coming soon TOC 2025-02-26 18:03:27 +07:00
Trisha Lim
bc2f37df37 set TOC items in context 2025-02-26 17:59:52 +07:00
Trisha Lim
57ffac4432 move docs layout 2025-02-26 17:43:38 +07:00
131 changed files with 2518 additions and 752 deletions

View File

@@ -1,5 +0,0 @@
---
"jazz-inspector": patch
---
add z-index of 1 to inspector

View File

@@ -1,5 +0,0 @@
---
"jazz-tools": patch
---
Throw when assigning invalid values to ref fields

View File

@@ -1,5 +0,0 @@
---
"jazz-tools": patch
---
Use RegisteredAccount types for `by` props

View File

@@ -1,5 +0,0 @@
---
"jazz-example-chat": patch
---
In the chat app example, show a "Message not readable" placeholder, if messages from a chat list are not readable by the user.

View File

@@ -1,5 +0,0 @@
---
"jazz-tools": patch
---
Fixes coList.splice to handle insertions at start of list

View File

@@ -1,5 +1,26 @@
# chat-rn-clerk
## 1.0.86
### Patch Changes
- jazz-react-native@0.11.5
- jazz-react-native-auth-clerk@0.11.5
- jazz-tools@0.11.5
- jazz-react-native-media-images@0.11.5
## 1.0.85
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react-native@0.11.4
- jazz-react-native-auth-clerk@0.11.4
- jazz-react-native-media-images@0.11.4
## 1.0.84
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-clerk",
"main": "index.js",
"version": "1.0.84",
"version": "1.0.86",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",

View File

@@ -1,5 +1,22 @@
# chat-rn
## 1.0.82
### Patch Changes
- jazz-react-native@0.11.5
- jazz-tools@0.11.5
## 1.0.81
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react-native@0.11.4
## 1.0.80
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.80",
"version": "1.0.82",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",

View File

@@ -25,11 +25,8 @@ appId: com.jazz.chatrn
- assertVisible: "Logout"
# send a message
- runFlow:
label: "Erase existing message"
file: erase_text.yml
env:
id: "message-input"
- tapOn:
id: "message-input"
- inputText: "bro, low key, it do be like that tho"
- tapOn:
id: "send-button"
@@ -44,5 +41,10 @@ appId: com.jazz.chatrn
# logout
- tapOn: "Logout"
- assertVisible: "boorad"
- assertVisible: "bro, low key, it do be like that tho"
- assertVisible: "Anonymous user"
# This doesn't work on CI, maybe because Android has a different alert dialog
# - tapOn: "Join chat"
# - inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
# - pressKey: "enter"
# - assertVisible: "boorad"
# - assertVisible: "bro, low key, it do be like that tho"

View File

@@ -1,5 +1,24 @@
# chat-vue
## 0.0.67
### Patch Changes
- jazz-browser@0.11.5
- jazz-tools@0.11.5
- jazz-vue@0.11.5
## 0.0.66
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser@0.11.4
- jazz-vue@0.11.4
## 0.0.65
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-vue",
"version": "0.0.65",
"version": "0.0.67",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,25 @@
# jazz-example-chat
## 0.0.164
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
- jazz-browser-media-images@0.11.5
## 0.0.163
### Patch Changes
- 2074e45: In the chat app example, show a "Message not readable" placeholder, if messages from a chat list are not readable by the user.
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser-media-images@0.11.4
- jazz-react@0.11.4
## 0.0.162
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.162",
"version": "0.0.164",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,6 +16,7 @@
"clsx": "^2.0.0",
"hash-slash": "workspace:*",
"jazz-browser-media-images": "workspace:*",
"jazz-inspector": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-react": "^0.274.0",

View File

@@ -1,6 +1,7 @@
import { apiKey } from "@/apiKey.ts";
import { getRandomUsername, inIframe, onChatLoad } from "@/util.ts";
import { useIframeHashRouter } from "hash-slash";
import { JazzInspector } from "jazz-inspector";
import { JazzProvider, useAccount } from "jazz-react";
import { Group, ID } from "jazz-tools";
import { StrictMode } from "react";
@@ -61,6 +62,7 @@ createRoot(document.getElementById("root")!).render(
defaultProfileName={defaultProfileName}
>
<App />
<JazzInspector />
</JazzProvider>
</StrictMode>
</ThemeProvider>,

View File

@@ -1,5 +1,24 @@
# minimal-auth-clerk
## 0.0.63
### Patch Changes
- jazz-react@0.11.5
- jazz-react-auth-clerk@0.11.5
- jazz-tools@0.11.5
## 0.0.62
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react@0.11.4
- jazz-react-auth-clerk@0.11.4
## 0.0.61
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "clerk",
"private": true,
"version": "0.0.61",
"version": "0.0.63",
"type": "module",
"scripts": {
"dev": "vite",
@@ -13,7 +13,7 @@
"dependencies": {
"@clerk/clerk-react": "^5.4.1",
"jazz-react": "workspace:*",
"jazz-react-auth-clerk": "workspace:0.11.3",
"jazz-react-auth-clerk": "workspace:0.11.5",
"jazz-tools": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"

View File

@@ -1,5 +1,22 @@
# file-share-svelte
## 0.0.47
### Patch Changes
- jazz-svelte@0.11.5
- jazz-tools@0.11.5
## 0.0.46
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-svelte@0.11.4
## 0.0.45
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "file-share-svelte",
"version": "0.0.45",
"version": "0.0.47",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,22 @@
# jazz-tailwind-demo-auth-starter
## 0.0.3
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
## 0.0.2
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react@0.11.4
## 0.0.52
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "filestream",
"private": true,
"version": "0.0.1",
"version": "0.0.3",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,24 @@
# form
## 0.1.5
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
- jazz-browser-media-images@0.11.5
## 0.1.4
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser-media-images@0.11.4
- jazz-react@0.11.4
## 0.1.3
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "form",
"private": true,
"version": "0.1.3",
"version": "0.1.5",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,24 @@
# image-upload
## 0.0.61
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
- jazz-browser-media-images@0.11.5
## 0.0.60
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser-media-images@0.11.4
- jazz-react@0.11.4
## 0.0.59
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "image-upload",
"private": true,
"version": "0.0.59",
"version": "0.0.61",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,21 @@
# jazz-example-inspector
## 0.0.114
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
- cojson-transport-ws@0.11.5
## 0.0.113
### Patch Changes
- Updated dependencies [7f036c1]
- cojson@0.11.4
- cojson-transport-ws@0.11.4
## 0.0.112
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector-app",
"private": true,
"version": "0.0.112",
"version": "0.0.114",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,8 +16,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.11.3",
"cojson-transport-ws": "workspace:0.11.3",
"cojson": "workspace:0.11.5",
"cojson-transport-ws": "workspace:0.11.5",
"hash-slash": "workspace:0.2.2",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",

View File

@@ -1,5 +1,24 @@
# multiauth
## 0.0.4
### Patch Changes
- jazz-react@0.11.5
- jazz-react-auth-clerk@0.11.5
- jazz-tools@0.11.5
## 0.0.3
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react@0.11.4
- jazz-react-auth-clerk@0.11.4
## 0.0.2
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "multiauth",
"private": true,
"version": "0.0.2",
"version": "0.0.4",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,26 @@
# jazz-example-musicplayer
## 0.0.85
### Patch Changes
- jazz-inspector@0.11.5
- jazz-react@0.11.5
- jazz-tools@0.11.5
## 0.0.84
### Patch Changes
- Updated dependencies [d133d47]
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- Updated dependencies [e902405]
- jazz-inspector@0.11.4
- jazz-tools@0.11.4
- jazz-react@0.11.4
## 0.0.83
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.83",
"version": "0.0.85",
"type": "module",
"scripts": {
"dev": "vite",
@@ -22,8 +22,8 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-inspector": "workspace:*",
"jazz-react": "workspace:0.11.3",
"jazz-tools": "workspace:0.11.3",
"jazz-react": "workspace:0.11.5",
"jazz-tools": "workspace:0.11.5",
"lucide-react": "^0.274.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",

View File

@@ -1,5 +1,22 @@
# organization
## 0.0.57
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
## 0.0.56
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react@0.11.4
## 0.0.55
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "organization",
"private": true,
"version": "0.0.55",
"version": "0.0.57",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,17 @@
# passkey-svelte
## 0.0.51
### Patch Changes
- jazz-svelte@0.11.5
## 0.0.50
### Patch Changes
- jazz-svelte@0.11.4
## 0.0.49
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "passkey-svelte",
"version": "0.0.49",
"version": "0.0.51",
"type": "module",
"private": true,
"scripts": {

View File

@@ -1,5 +1,22 @@
# minimal-auth-passkey
## 0.0.62
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
## 0.0.61
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react@0.11.4
## 0.0.60
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passkey",
"private": true,
"version": "0.0.60",
"version": "0.0.62",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,22 @@
# passphrase
## 0.0.59
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
## 0.0.58
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react@0.11.4
## 0.0.57
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passphrase",
"private": true,
"version": "0.0.57",
"version": "0.0.59",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,22 @@
# jazz-password-manager
## 0.0.83
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
## 0.0.82
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react@0.11.4
## 0.0.81
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.81",
"version": "0.0.83",
"type": "module",
"scripts": {
"dev": "vite",
@@ -12,8 +12,8 @@
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
},
"dependencies": {
"jazz-react": "workspace:0.11.3",
"jazz-tools": "workspace:0.11.3",
"jazz-react": "workspace:0.11.5",
"jazz-tools": "workspace:0.11.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.41.5",

View File

@@ -1,5 +1,24 @@
# jazz-example-pets
## 0.0.181
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
- jazz-browser-media-images@0.11.5
## 0.0.180
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser-media-images@0.11.4
- jazz-react@0.11.4
## 0.0.179
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.179",
"version": "0.0.181",
"type": "module",
"scripts": {
"dev": "vite",
@@ -19,9 +19,9 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.11.3",
"jazz-react": "workspace:0.11.3",
"jazz-tools": "workspace:0.11.3",
"jazz-browser-media-images": "workspace:0.11.5",
"jazz-react": "workspace:0.11.5",
"jazz-tools": "workspace:0.11.5",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.3.1",
@@ -41,7 +41,7 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.20",
"is-ci": "^3.0.1",
"jazz-run": "workspace:0.11.3",
"jazz-run": "workspace:0.11.5",
"postcss": "^8.4.27",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2",

View File

@@ -1,5 +1,24 @@
# reactions
## 0.0.61
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
- jazz-browser-media-images@0.11.5
## 0.0.60
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser-media-images@0.11.4
- jazz-react@0.11.4
## 0.0.59
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "reactions",
"private": true,
"version": "0.0.59",
"version": "0.0.61",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,24 @@
# todo-vue
## 0.0.65
### Patch Changes
- jazz-browser@0.11.5
- jazz-tools@0.11.5
- jazz-vue@0.11.5
## 0.0.64
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser@0.11.4
- jazz-vue@0.11.4
## 0.0.63
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "todo-vue",
"version": "0.0.63",
"version": "0.0.65",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,22 @@
# jazz-example-todo
## 0.0.180
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
## 0.0.179
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react@0.11.4
## 0.0.178
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.178",
"version": "0.0.180",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,8 +16,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.11.3",
"jazz-tools": "workspace:0.11.3",
"jazz-react": "workspace:0.11.5",
"jazz-tools": "workspace:0.11.5",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.3.1",

View File

@@ -1,5 +1,26 @@
# version-history
## 0.0.58
### Patch Changes
- jazz-inspector@0.11.5
- jazz-react@0.11.5
- jazz-tools@0.11.5
## 0.0.57
### Patch Changes
- Updated dependencies [d133d47]
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- Updated dependencies [e902405]
- jazz-inspector@0.11.4
- jazz-tools@0.11.4
- jazz-react@0.11.4
## 0.0.56
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "version-history",
"private": true,
"version": "0.0.56",
"version": "0.0.58",
"type": "module",
"scripts": {
"dev": "vite",
@@ -12,6 +12,7 @@
},
"dependencies": {
"@tailwindcss/forms": "^0.5.9",
"jazz-inspector": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"react": "^18.3.1",

View File

@@ -80,7 +80,7 @@ export function IssueVersionHistory({ id }: { id: ID<Issue> }) {
<div className="flex flex-col text-sm gap-2">
<h2 className="sr-only">Edits</h2>
{edits.map((edit, i) => (
<div>
<div key={i}>
<p className="text-xs text-stone-400">
{edit.madeAt.toLocaleString()}
</p>

View File

@@ -3,6 +3,7 @@ import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { JazzInspector } from "jazz-inspector";
import { apiKey } from "./apiKey.ts";
createRoot(document.getElementById("root")!).render(
@@ -16,6 +17,7 @@ createRoot(document.getElementById("root")!).render(
<DemoAuthBasicUI appName="Jazz Version History Example">
<App />
</DemoAuthBasicUI>
<JazzInspector position="bottom right" />
</JazzProvider>
</StrictMode>,
);

View File

@@ -1,3 +1,4 @@
import { TocProvider } from "@/components/TocProvider";
import { ApiNav } from "@/components/docs/ApiNav";
import DocsLayout from "@/components/docs/DocsLayout";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
@@ -8,8 +9,10 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<DocsLayout nav={<ApiNav />} navIcon="package" navName="API Ref">
<Prose className="overflow-x-hidden lg:flex-1 py-10">{children}</Prose>
</DocsLayout>
<TocProvider>
<DocsLayout nav={<ApiNav />} navIcon="package" navName="API Ref">
<Prose className="overflow-x-hidden lg:flex-1 py-10">{children}</Prose>
</DocsLayout>
</TocProvider>
);
}

View File

@@ -1,10 +1,8 @@
import DocsLayout from "@/components/docs/DocsLayout";
import { TocItemsSetter } from "@/components/docs/TocItemsSetter";
import ComingSoonPage from "@/components/docs/coming-soon.mdx";
import { DocNav } from "@/components/docs/nav";
import { docNavigationItems } from "@/lib/docNavigationItems.js";
import { Framework, frameworks } from "@/lib/framework";
import type { Toc } from "@stefanprobst/rehype-extract-toc";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
async function getMdxSource(slugPath: string, framework: string) {
// Try to import the framework-specific file first
@@ -46,7 +44,6 @@ export default async function Page({
}: { params: Promise<{ slug: string[]; framework: string }> }) {
const { slug, framework } = await params;
const slugPath = slug.join("/");
const bodyClassName = "overflow-x-hidden lg:flex-1 py-10 max-w-3xl mx-auto";
try {
const mdxSource = await getMdxSource(slugPath, framework);
@@ -56,19 +53,17 @@ export default async function Page({
const tocItems = (tableOfContents as Toc)?.[0]?.children;
return (
<DocsLayout toc={tocItems} nav={<DocNav />}>
<Prose className={bodyClassName}>
<Content />
</Prose>
</DocsLayout>
<>
<TocItemsSetter items={tocItems} />
<Content />
</>
);
} catch (error) {
return (
<DocsLayout nav={<DocNav />}>
<Prose className={bodyClassName}>
<ComingSoonPage />
</Prose>
</DocsLayout>
<>
<TocItemsSetter items={[]} />
<ComingSoonPage />
</>
);
}
}

View File

@@ -0,0 +1,310 @@
import { CodeGroup } from "@/components/forMdx";
export const metadata = { title: "FileStreams" };
# FileStreams
FileStreams handle binary data in Jazz applications - think documents, audio files, and other non-text content. They're essentially collaborative versions of `Blob`s that sync automatically across devices.
Use FileStreams when you need to:
- Distribute documents across devices
- Store audio or video files
- Sync any binary data between users
**Note:** For images specifically, Jazz provides the higher-level `ImageDefinition` abstraction which manages multiple image resolutions - see the [ImageDefinition documentation](/docs/using-covalues/imagedefinition) for details.
FileStreams provide automatic chunking when using the `createFromBlob` method, track upload progress, and handle MIME types and metadata.
In your schema, reference FileStreams like any other CoValue:
<CodeGroup>
```ts
import { CoMap, FileStream, co } from "jazz-tools";
class Document extends CoMap {
title = co.string;
file = co.ref(FileStream); // Store a document file
}
```
</CodeGroup>
## Creating FileStreams
There are two main ways to create FileStreams: creating empty ones for manual data population or creating directly from existing files or blobs.
### Creating from Blobs and Files
For files from input elements or drag-and-drop interfaces, use `createFromBlob`:
<CodeGroup>
```ts
// From a file input
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async () => {
const file = fileInput.files[0];
if (file) {
// Create FileStream from user-selected file
const fileStream = await FileStream.createFromBlob(file);
// Or with progress tracking for better UX
const fileWithProgress = await FileStream.createFromBlob(file, {
onProgress: (progress) => {
// progress is a value between 0 and 1
const percent = Math.round(progress * 100);
console.log(`Upload progress: ${percent}%`);
progressBar.style.width = `${percent}%`;
}
});
}
});
```
</CodeGroup>
### Creating Empty FileStreams
Create an empty FileStream when you want to manually [add binary data in chunks](/docs/using-covalues/filestreams#writing-to-filestreams):
<CodeGroup>
```ts
import { FileStream } from "jazz-tools";
// Create a new empty FileStream
const fileStream = FileStream.create();
```
</CodeGroup>
## Reading from FileStreams
`FileStream`s provide several ways to access their binary content, from raw chunks to convenient Blob objects.
### Getting Raw Data Chunks
To access the raw binary data and metadata:
<CodeGroup>
```ts
// Get all chunks and metadata
const fileData = fileStream.getChunks();
if (fileData) {
console.log(`MIME type: ${fileData.mimeType}`);
console.log(`Total size: ${fileData.totalSizeBytes} bytes`);
console.log(`File name: ${fileData.fileName}`);
console.log(`Is complete: ${fileData.finished}`);
// Access raw binary chunks
for (const chunk of fileData.chunks) {
// Each chunk is a Uint8Array
console.log(`Chunk size: ${chunk.length} bytes`);
}
}
```
</CodeGroup>
By default, `getChunks()` only returns data for completely synced `FileStream`s. To start using chunks from a `FileStream` that's currently still being synced use the `allowUnfinished` option:
<CodeGroup>
```ts
// Get data even if the stream isn't complete
const partialData = fileStream.getChunks({ allowUnfinished: true });
```
</CodeGroup>
### Converting to Blobs
For easier integration with web APIs, convert to a `Blob`:
<CodeGroup>
```ts
// Convert to a Blob
const blob = fileStream.toBlob();
if (blob) {
// Use with URL.createObjectURL
const url = URL.createObjectURL(blob);
// Create a download link
const link = document.createElement('a');
link.href = url;
link.download = fileData?.fileName || 'document.pdf';
link.click();
// Clean up when done
URL.revokeObjectURL(url);
}
```
</CodeGroup>
### Loading FileStreams as Blobs
You can directly load a `FileStream` as a `Blob` when you only have its ID:
<CodeGroup>
```ts
// Load directly as a Blob when you have an ID
const blob = await FileStream.loadAsBlob(fileStreamId);
// By default, waits for complete uploads
// For in-progress uploads:
const partialBlob = await FileStream.loadAsBlob(fileStreamId, {
allowUnfinished: true
});
```
</CodeGroup>
### Checking Completion Status
Check if a `FileStream` is fully synced:
<CodeGroup>
```ts
if (fileStream.isBinaryStreamEnded()) {
console.log('File is completely synced');
} else {
console.log('File upload is still in progress');
}
```
</CodeGroup>
## Writing to FileStreams
When creating a `FileStream` manually (not using `createFromBlob`), you need to manage the upload process yourself. This gives you more control over chunking and progress tracking.
### The Upload Lifecycle
`FileStream` uploads follow a three-stage process:
1. **Start** - Initialize with metadata
2. **Push** - Send one or more chunks of data
3. **End** - Mark the stream as complete
### Starting a `FileStream`
Begin by providing metadata about the file:
<CodeGroup>
```ts
// Create an empty FileStream
const fileStream = FileStream.create();
// Initialize with metadata
fileStream.start({
mimeType: 'application/pdf', // MIME type (required)
totalSizeBytes: 1024 * 1024 * 2, // Size in bytes (if known)
fileName: 'document.pdf' // Original filename (optional)
});
```
</CodeGroup>
### Pushing Data
Add binary data in chunks - this helps with large files and progress tracking:
<CodeGroup>
```ts
// Create a sample Uint8Array (in real apps, this would be file data)
const data = new Uint8Array([...]);
// For large files, break into chunks (e.g., 100KB each)
const chunkSize = 1024 * 100;
for (let i = 0; i < data.length; i += chunkSize) {
// Create a slice of the data
const chunk = data.slice(i, i + chunkSize);
// Push chunk to the FileStream
fileStream.push(chunk);
// Track progress
const progress = Math.min(100, Math.round((i + chunk.length) * 100 / data.length));
console.log(`Upload progress: ${progress}%`);
}
```
</CodeGroup>
### Completing the Upload
Once all chunks are pushed, mark the `FileStream` as complete:
<CodeGroup>
```ts
// Finalize the upload
fileStream.end();
console.log('Upload complete!');
```
</CodeGroup>
## Subscribing to `FileStream`s
Like other CoValues, you can subscribe to `FileStream`s to get notified of changes as they happen. This is especially useful for tracking upload progress when someone else is uploading a file.
### Loading by ID
Load a `FileStream` when you have its ID:
<CodeGroup>
```ts
// Load a FileStream by ID
const fileStream = await FileStream.load(fileStreamId, []);
if (fileStream) {
console.log('FileStream loaded successfully');
// Check if it's complete
if (fileStream.isBinaryStreamEnded()) {
// Process the completed file
const blob = fileStream.toBlob();
}
}
```
</CodeGroup>
### Subscribing to Changes
Subscribe to a `FileStream` to be notified when chunks are added or when the upload is complete:
<CodeGroup>
```ts
// Subscribe to a FileStream by ID
const unsubscribe = FileStream.subscribe(fileStreamId, [], (fileStream) => {
// Called whenever the FileStream changes
console.log('FileStream updated');
// Get current status
const chunks = fileStream.getChunks({ allowUnfinished: true });
if (chunks) {
const uploadedBytes = chunks.chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const totalBytes = chunks.totalSizeBytes || 1;
const progress = Math.min(100, Math.round(uploadedBytes * 100 / totalBytes));
console.log(`Upload progress: ${progress}%`);
if (fileStream.isBinaryStreamEnded()) {
console.log('Upload complete!');
// Now safe to use the file
const blob = fileStream.toBlob();
// Clean up the subscription if we're done
unsubscribe();
}
}
});
```
</CodeGroup>
### Waiting for Upload Completion
If you need to wait for a `FileStream` to be fully synchronized across devices:
<CodeGroup>
```ts
// Wait for the FileStream to be fully synced
await fileStream.waitForSync({
timeout: 5000 // Optional timeout in ms
});
console.log('FileStream is now synced to all connected devices');
```
</CodeGroup>
This is useful when you need to ensure that a file is available to other users before proceeding with an operation.

View File

@@ -0,0 +1,213 @@
import { CodeGroup } from "@/components/forMdx";
export const metadata = { title: "ImageDefinition" };
# ImageDefinition
`ImageDefinition` is a specialized CoValue designed specifically for managing images in Jazz applications. It extends beyond basic file storage by supporting multiple resolutions of the same image and progressive loading patterns.
We also offer [`createImage()`](#creating-images), a higher-level function to create an `ImageDefinition` from a file.
If you're building with React, we recommend starting with our [React-specific image documentation](/docs/react/using-covalues/imagedef) which covers higher-level components and hooks for working with images.
The [Image Upload example](https://github.com/gardencmp/jazz/tree/main/examples/image-upload) demonstrates use of `ImageDefinition`.
## Creating Images
The easiest way to create and use images in your Jazz application is with the `createImage()` function:
<CodeGroup>
```ts
import { createImage } from "jazz-browser-media-images";
// Create an image from a file input
async function handleFileUpload(event) {
const file = event.target.files[0];
if (file) {
// Creates ImageDefinition with multiple resolutions automatically
const image = await createImage(file, {
owner: me.profile._owner,
});
// Store the image in your application data
me.profile.image = image;
}
}
```
</CodeGroup>
> Note: `createImage()` requires a browser environment as it uses browser APIs to process images.
The `createImage()` function:
- Creates an `ImageDefinition` with the right properties
- Generates a small placeholder for immediate display
- Creates multiple resolution variants of your image
- Returns the ID of the created `ImageDefinition`
### Configuration Options
You can configure `createImage()` with additional options:
<CodeGroup>
```tsx
// Configuration options
const options = {
owner: me, // Owner for access control
maxSize: 1024 // Maximum resolution to generate
};
// Setting maxSize controls which resolutions are generated:
// 256: Only creates the smallest resolution (256px on longest side)
// 1024: Creates 256px and 1024px resolutions
// 2048: Creates 256px, 1024px, and 2048px resolutions
// undefined: Creates all resolutions including the original size
const image = await createImage(file, options);
```
</CodeGroup>
## Creating ImageDefinitions
Create an `ImageDefinition` by specifying the original dimensions and an optional placeholder:
<CodeGroup>
```ts
import { ImageDefinition } from "jazz-tools";
// Create with original dimensions
const image = ImageDefinition.create({
originalSize: [1920, 1080],
});
// With a placeholder for immediate display
const imageWithPlaceholder = ImageDefinition.create({
originalSize: [1920, 1080],
placeholderDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAP...",
});
```
</CodeGroup>
### Structure
`ImageDefinition` stores:
- The original image dimensions (`originalSize`)
- An optional placeholder (`placeholderDataURL`, typically a tiny base64-encoded preview)
- Multiple resolution variants of the same image as [`FileStream`s](./using-covalues/filestream)
Each resolution is stored with a key in the format `"widthxheight"` (e.g., `"1920x1080"`, `"800x450"`).
<CodeGroup>
```ts
import { CoMap, CoList, ImageDefinition, co } from "jazz-tools";
class ListOfImages extends CoList.Of(co.ref(ImageDefinition)) {}
class Gallery extends CoMap {
title = co.string;
images = co.ref(ListOfImages);
}
```
</CodeGroup>
## Adding Image Resolutions
Add multiple resolutions to an `ImageDefinition` by creating `FileStream`s for each size:
<CodeGroup>
```ts
// Create FileStreams for different resolutions
const fullRes = await FileStream.createFromBlob(fullSizeBlob);
const mediumRes = await FileStream.createFromBlob(mediumSizeBlob);
const thumbnailRes = await FileStream.createFromBlob(thumbnailBlob);
// Add to the ImageDefinition with appropriate resolution keys
image["1920x1080"] = fullRes;
image["800x450"] = mediumRes;
image["320x180"] = thumbnailRes;
```
</CodeGroup>
## Retrieving Images
The `highestResAvailable` method helps select the best image resolution for the current context:
<CodeGroup>
```ts
// Get the highest resolution available
const highestRes = image.highestResAvailable();
if (highestRes) {
console.log(`Found resolution: ${highestRes.res}`);
// Convert to a usable blob
const blob = highestRes.stream.toBlob();
if (blob) {
const url = URL.createObjectURL(blob);
imageElement.src = url;
// Remember to revoke the URL when no longer needed
imageElement.onload = () => {
URL.revokeObjectURL(url);
};
}
}
// Or constrain by maximum width
const maxWidth = window.innerWidth;
const appropriateRes = image.highestResAvailable({ maxWidth });
```
</CodeGroup>
### Fallback Behavior
`highestResAvailable` returns the largest resolution that fits your constraints. If a resolution has incomplete data, it falls back to the next available lower resolution.
<CodeGroup>
```ts
const image = ImageDefinition.create({
originalSize: [1920, 1080],
});
image["1920x1080"] = FileStream.create(); // Empty image upload
image["800x450"] = await FileStream.createFromBlob(mediumSizeBlob);
const highestRes = image.highestResAvailable();
console.log(highestRes.res); // 800x450
```
</CodeGroup>
## Progressive Loading Patterns
`ImageDefinition` supports simple progressive loading with placeholders and resolution selection:
<CodeGroup>
```ts
// Start with placeholder for immediate display
if (image.placeholderDataURL) {
imageElement.src = image.placeholderDataURL;
}
// Then load the best resolution for the current display
const screenWidth = window.innerWidth;
const bestRes = image.highestResAvailable({ maxWidth: screenWidth });
if (bestRes) {
const blob = bestRes.stream.toBlob();
if (blob) {
const url = URL.createObjectURL(blob);
imageElement.src = url;
// Remember to revoke the URL when no longer needed
imageElement.onload = () => {
URL.revokeObjectURL(url);
};
}
}
```
</CodeGroup>
## Best Practices
- **Generate resolutions server-side** when possible for optimal quality
- **Use placeholders** (like LQIP - Low Quality Image Placeholders) for instant rendering
- **Prioritize loading** the resolution appropriate for the current viewport
- **Consider device pixel ratio** (window.devicePixelRatio) for high-DPI displays
- **Always call URL.revokeObjectURL** after the image loads to prevent memory leaks

View File

@@ -0,0 +1,233 @@
import { CodeGroup } from "@/components/forMdx";
export const metadata = { title: "ImageDefinition" };
# ImageDefinition
`ImageDefinition` is a specialized CoValue designed specifically for managing images in Jazz. It extends beyond basic file storage by supporting multiple resolutions of the same image, optimized for mobile devices.
Jazz offers several tools to work with images in React Native:
- [`createImage()`](#creating-images) - function to create an `ImageDefinition` from a base64 image data URI
- [`ProgressiveImg`](#displaying-images-with-progressiveimg) - React component to display an image with progressive loading
- [`useProgressiveImg`](#using-useprogressiveimg-hook) - React hook to load an image in your own component
For an example of use, see our [React Native Clerk Chat example](https://github.com/gardencmp/jazz/tree/main/examples/chat-rn-clerk).
## Creating Images
The easiest way to create and use images in your Jazz application is with the `createImage()` function:
<CodeGroup>
```tsx
import { createImage } from "jazz-react-native-media-images";
import * as ImagePicker from 'expo-image-picker';
async function handleImagePicker() {
try {
// Launch the image picker
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
base64: true,
quality: 1,
});
if (!result.canceled) {
const base64Uri = `data:image/jpeg;base64,${result.assets[0].base64}`;
// Creates ImageDefinition with multiple resolutions automatically
const image = await createImage(base64Uri, {
owner: me.profile._owner,
maxSize: 2048, // Optional: limit maximum resolution
});
// Store the image
me.profile.image = image;
}
} catch (error) {
console.error("Error creating image:", error);
}
}
```
</CodeGroup>
The `createImage()` function:
- Creates an `ImageDefinition` with the right properties
- Generates a small placeholder for immediate display
- Creates multiple resolution variants of your image
- Returns the created `ImageDefinition`
### Configuration Options
You can configure `createImage()` with additional options:
<CodeGroup>
```tsx
// Configuration options
const options = {
owner: me, // Owner for access control
maxSize: 1024 // Maximum resolution to generate
};
// Setting maxSize controls which resolutions are generated:
// 256: Only creates the smallest resolution (256px on longest side)
// 1024: Creates 256px and 1024px resolutions
// 2048: Creates 256px, 1024px, and 2048px resolutions
// undefined: Creates all resolutions including the original size
const image = await createImage(base64Uri, options);
```
</CodeGroup>
## Displaying Images with `ProgressiveImg`
For a complete progressive loading experience, use the `ProgressiveImg` component:
<CodeGroup>
```tsx
import { ProgressiveImg } from "jazz-react-native";
import { Image, StyleSheet } from "react-native";
function GalleryView({ image }) {
return (
<ProgressiveImg
image={image} // The image definition to load
maxWidth={800} // Limit to resolutions up to 800px wide
>
{({ src }) => (
<Image
source={{ uri: src }}
style={styles.galleryImage}
resizeMode="cover"
/>
)}
</ProgressiveImg>
);
}
const styles = StyleSheet.create({
galleryImage: {
width: '100%',
height: 200,
borderRadius: 8,
}
});
```
</CodeGroup>
The `ProgressiveImg` component handles:
- Showing a placeholder while loading
- Automatically selecting the appropriate resolution
- Progressive enhancement as higher resolutions become available
- Cleaning up resources when unmounted
## Using `useProgressiveImg` Hook
For more control over image loading, you can implement your own progressive image component:
<CodeGroup>
```tsx
import { useProgressiveImg } from "jazz-react-native";
import { Image, View, Text, ActivityIndicator } from "react-native";
function CustomImageComponent({ image }) {
const {
src, // Data URI containing the image data as a base64 string,
// or a placeholder image URI
res, // The current resolution
originalSize // The original size of the image
} = useProgressiveImg({
image: image, // The image definition to load
maxWidth: 800 // Limit to resolutions up to 800px wide
});
// When image is not available yet
if (!src) {
return (
<View style={{ height: 200, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f0f0f0' }}>
<ActivityIndicator size="small" color="#0000ff" />
<Text style={{ marginTop: 10 }}>Loading image...</Text>
</View>
);
}
// When using placeholder
if (res === "placeholder") {
return (
<View style={{ position: 'relative' }}>
<Image
source={{ uri: src }}
style={{ width: '100%', height: 200, opacity: 0.7 }}
resizeMode="cover"
/>
<ActivityIndicator
size="large"
color="#ffffff"
style={{ position: 'absolute', top: '50%', left: '50%', marginLeft: -20, marginTop: -20 }}
/>
</View>
);
}
// Full image display with custom overlay
return (
<View style={{ position: 'relative', width: '100%', height: 200 }}>
<Image
source={{ uri: src }}
style={{ width: '100%', height: '100%' }}
resizeMode="cover"
/>
<View style={{ position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: 'rgba(0,0,0,0.5)', padding: 8 }}>
<Text style={{ color: 'white' }}>Resolution: {res}</Text>
</View>
</View>
);
}
```
</CodeGroup>
## Understanding ImageDefinition
Behind the scenes, `ImageDefinition` is a specialized CoValue that stores:
- The original image dimensions (`originalSize`)
- An optional placeholder (`placeholderDataURL`) for immediate display
- Multiple resolution variants of the same image as [`FileStream`s](../using-covalues/filestreams)
Each resolution is stored with a key in the format `"widthxheight"` (e.g., `"1920x1080"`, `"800x450"`).
<CodeGroup>
```tsx
// Structure of an ImageDefinition
const image = ImageDefinition.create({
originalSize: [1920, 1080],
placeholderDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
});
// Accessing the highest available resolution
const highestRes = image.highestResAvailable();
if (highestRes) {
console.log(`Found resolution: ${highestRes.res}`);
console.log(`Stream: ${highestRes.stream}`);
}
```
</CodeGroup>
For more details on using `ImageDefinition` directly, see the [VanillaJS docs](/docs/vanilla/using-covalues/imagedef).
### Fallback Behavior
`highestResAvailable` returns the largest resolution that fits your constraints. If a resolution has incomplete data, it falls back to the next available lower resolution.
<CodeGroup>
```tsx
const image = ImageDefinition.create({
originalSize: [1920, 1080],
});
image["1920x1080"] = FileStream.create(); // Empty image upload
image["800x450"] = await FileStream.createFromBlob(mediumSizeBlob);
const highestRes = image.highestResAvailable();
console.log(highestRes.res); // 800x450
```
</CodeGroup>

View File

@@ -0,0 +1,196 @@
import { CodeGroup } from "@/components/forMdx";
export const metadata = { title: "ImageDefinition" };
# ImageDefinition
`ImageDefinition` is a specialized CoValue designed specifically for managing images in Jazz applications. It extends beyond basic file storage by supporting multiple resolutions of the same image and progressive loading patterns.
Beyond [`ImageDefinition`](#understanding-imagedefinition), Jazz offers higher-level functions and components that make it easier to use images:
- [`createImage()`](#creating-images) - function to create an `ImageDefinition` from a file
- [`ProgressiveImg`](#displaying-images-with-progressiveimg) - React component to display an image with progressive loading
- [`useProgressiveImg`](#using-useprogressiveimg-hook) - React hook to load an image in your own component
The [Image Upload example](https://github.com/gardencmp/jazz/tree/main/examples/image-upload) demonstrates use of `ProgressiveImg` and `ImageDefinition`.
## Creating Images
The easiest way to create and use images in your Jazz application is with the `createImage()` function:
<CodeGroup>
```ts
import { createImage } from "jazz-browser-media-images";
// Create an image from a file input
async function handleFileUpload(event) {
const file = event.target.files[0];
if (file) {
// Creates ImageDefinition with multiple resolutions automatically
const image = await createImage(file, {
owner: me.profile._owner,
});
// Store the image in your application data
me.profile.image = image;
}
}
```
</CodeGroup>
> Note: `createImage()` requires a browser environment as it uses browser APIs to process images.
The `createImage()` function:
- Creates an `ImageDefinition` with the right properties
- Generates a small placeholder for immediate display
- Creates multiple resolution variants of your image
- Returns the created `ImageDefinition`
### Configuration Options
You can configure `createImage()` with additional options:
<CodeGroup>
```tsx
// Configuration options
const options = {
owner: me, // Owner for access control
maxSize: 1024 // Maximum resolution to generate
};
// Setting maxSize controls which resolutions are generated:
// 256: Only creates the smallest resolution (256px on longest side)
// 1024: Creates 256px and 1024px resolutions
// 2048: Creates 256px, 1024px, and 2048px resolutions
// undefined: Creates all resolutions including the original size
const image = await createImage(file, options);
```
</CodeGroup>
## Displaying Images with `ProgressiveImg`
For a complete progressive loading experience, use the `ProgressiveImg` component:
<CodeGroup>
```tsx
import { ProgressiveImg } from "jazz-react";
function GalleryView({ image }) {
return (
<div className="image-container">
<ProgressiveImg
image={image} // The image definition to load
maxWidth={800} // Limit to resolutions up to 800px wide
>
{({ src }) => (
<img
src={src}
alt="Gallery image"
className="gallery-image"
/>
)}
</ProgressiveImg>
</div>
);
}
```
</CodeGroup>
The `ProgressiveImg` component handles:
- Showing a placeholder while loading
- Automatically selecting the appropriate resolution
- Progressive enhancement as higher resolutions become available
- Cleaning up resources when unmounted
## Using `useProgressiveImg` Hook
For more control over image loading, you can implement your own progressive image component:
<CodeGroup>
```tsx
import { useProgressiveImg } from "jazz-react";
function CustomImageComponent({ image }) {
const {
src, // Data URI containing the image data as a base64 string,
// or a placeholder image URI
res, // The current resolution
originalSize // The original size of the image
} = useProgressiveImg({
image: image, // The image definition to load
maxWidth: 800 // Limit to resolutions up to 800px wide
});
// When image is not available yet
if (!src) {
return <div className="image-loading-fallback">Loading image...</div>;
}
// When image is loading, show a placeholder
if (res === "placeholder") {
return <img src={src} alt="Loading..." className="blur-effect" />;
}
// Full image display with custom overlay
return (
<div className="custom-image-wrapper">
<img
src={src}
alt="Custom image"
className="custom-image"
/>
<div className="image-overlay">
<span className="image-caption">Resolution: {res}</span>
</div>
</div>
);
}
```
</CodeGroup>
## Understanding ImageDefinition
Behind the scenes, `ImageDefinition` is a specialized CoValue that stores:
- The original image dimensions (`originalSize`)
- An optional placeholder (`placeholderDataURL`) for immediate display
- Multiple resolution variants of the same image as [`FileStream`s](../using-covalues/filestreams)
Each resolution is stored with a key in the format `"widthxheight"` (e.g., `"1920x1080"`, `"800x450"`).
<CodeGroup>
```ts
// Structure of an ImageDefinition
const image = ImageDefinition.create({
originalSize: [1920, 1080],
placeholderDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
});
// Accessing the highest available resolution
const highestRes = image.highestResAvailable();
if (highestRes) {
console.log(`Found resolution: ${highestRes.res}`);
console.log(`Stream: ${highestRes.stream}`);
}
```
</CodeGroup>
For more details on using `ImageDefinition` directly, see the [VanillaJS docs](/docs/vanilla/using-covalues/imagedef).
### Fallback Behavior
`highestResAvailable` returns the largest resolution that fits your constraints. If a resolution has incomplete data, it falls back to the next available lower resolution.
<CodeGroup>
```ts
const image = ImageDefinition.create({
originalSize: [1920, 1080],
});
image["1920x1080"] = FileStream.create(); // Empty image upload
image["800x450"] = await FileStream.createFromBlob(mediumSizeBlob);
const highestRes = image.highestResAvailable();
console.log(highestRes.res); // 800x450
```
</CodeGroup>

View File

@@ -1,3 +1,4 @@
import { TocProvider } from "@/components/TocProvider";
import DocsLayout from "@/components/docs/DocsLayout";
import { DocNav } from "@/components/docs/nav";
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
@@ -8,8 +9,12 @@ export default function Layout({
children: React.ReactNode;
}) {
return (
<DocsLayout nav={<DocNav />}>
<Prose className="max-w-3xl mx-auto lg:flex-1 py-10">{children}</Prose>
</DocsLayout>
<TocProvider>
<DocsLayout nav={<DocNav />}>
<Prose className="overflow-x-hidden lg:flex-1 py-10 max-w-3xl mx-auto">
{children}
</Prose>
</DocsLayout>
</TocProvider>
);
}

View File

@@ -0,0 +1,15 @@
"use client";
import { TocContext } from "@/lib/TocContext";
import type { Toc, TocEntry } from "@stefanprobst/rehype-extract-toc";
import { useState } from "react";
export function TocProvider({ children }: { children: React.ReactNode }) {
const [tocItems, setTocItems] = useState<Toc>();
return (
<TocContext.Provider value={{ tocItems, setTocItems }}>
{children}
</TocContext.Provider>
);
}

View File

@@ -1,6 +1,8 @@
"use client";
import { TableOfContents } from "@/components/docs/TableOfContents";
import { JazzNav } from "@/components/nav";
import { Toc } from "@stefanprobst/rehype-extract-toc";
import { useTocItems } from "@/lib/TocContext";
import { clsx } from "clsx";
export default function DocsLayout({
@@ -8,14 +10,14 @@ export default function DocsLayout({
nav,
navName,
navIcon,
toc,
}: {
children: React.ReactNode;
nav?: React.ReactNode;
navName?: string;
navIcon?: string;
toc?: Toc;
}) {
const { tocItems } = useTocItems();
const navSections = [
{
name: navName || "Docs",
@@ -24,8 +26,8 @@ export default function DocsLayout({
},
{
name: "Outline",
content: toc && (
<TableOfContents className="text-sm" items={toc as Toc} />
content: tocItems?.length && (
<TableOfContents className="text-sm" items={tocItems} />
),
icon: "tableOfContents",
},
@@ -48,11 +50,11 @@ export default function DocsLayout({
</div>
<div className={clsx("md:col-span-8 lg:col-span-9 flex gap-12")}>
{children}
{toc && (
{tocItems?.length && (
<>
<TableOfContents
className="pl-3 py-6 shrink-0 text-sm sticky align-start top-[72px] w-[16rem] h-[calc(100vh-72px)] overflow-y-auto hidden lg:block"
items={toc as Toc}
items={tocItems}
/>
</>
)}

View File

@@ -9,7 +9,7 @@ const TocList = ({
items,
level,
currentId,
}: { items: TocEntry[]; level: number; currentId: string }) => {
}: { items: Toc; level: number; currentId: string }) => {
const isActive = (item: TocEntry) => {
if (!item.id) return false;
if (item.id === currentId) return true;
@@ -20,7 +20,7 @@ const TocList = ({
};
return (
<ul className="space-y-3" style={{ paddingLeft: `${level * 1}rem` }}>
<ul className="space-y-3" style={{ paddingLeft: `${level}rem` }}>
{items.map((item) => (
<li key={item.id} className="space-y-3">
{item.id && (

View File

@@ -0,0 +1,15 @@
"use client";
import { useTocItems } from "@/lib/TocContext";
import type { Toc } from "@stefanprobst/rehype-extract-toc";
import { useEffect } from "react";
export function TocItemsSetter({ items }: { items: Toc | undefined }) {
const { setTocItems } = useTocItems();
useEffect(() => {
setTocItems(items);
}, [items, setTocItems]);
return null;
}

View File

@@ -0,0 +1,17 @@
import type { Toc } from "@stefanprobst/rehype-extract-toc";
import { createContext, useContext } from "react";
type TocContextType = {
tocItems: Toc | undefined;
setTocItems: (items: Toc | undefined) => void;
};
export const TocContext = createContext<TocContextType | undefined>(undefined);
export function useTocItems() {
const context = useContext(TocContext);
if (context === undefined) {
throw new Error("useTocItems must be used within a TocProvider");
}
return context;
}

View File

@@ -14,9 +14,6 @@ export const docNavigationItems = [
href: "/docs/guide",
done: {
react: 100,
"react-native": 0,
vue: 0,
svelte: 0,
},
},
{
@@ -120,22 +117,31 @@ export const docNavigationItems = [
{
name: "CoMaps",
href: "/docs/using-covalues/comaps",
done: 80,
done: 100,
},
{
name: "CoLists",
href: "/docs/using-covalues/colists",
done: 80,
done: 100,
},
{
name: "CoFeeds",
href: "/docs/using-covalues/cofeeds",
done: 80,
done: 100,
},
{
name: "FileStreams",
href: "/docs/using-covalues/filestreams",
done: 0,
done: 80,
},
{
name: "ImageDefinition",
href: "/docs/using-covalues/imagedef",
done: {
react: 100,
"react-native": 100,
vanilla: 100,
},
},
{
name: "SchemaUnions",
@@ -152,11 +158,6 @@ export const docNavigationItems = [
href: "/docs/using-covalues/history-and-time-travel",
done: 0,
},
{
name: "Access control",
href: "/docs/using-covalues/access-control",
done: 0,
},
],
},
{

View File

@@ -1,5 +1,21 @@
# cojson-storage-indexeddb
## 0.11.5
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
- cojson-storage@0.11.5
## 0.11.4
### Patch Changes
- Updated dependencies [7f036c1]
- cojson@0.11.4
- cojson-storage@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.11.3",
"version": "0.11.5",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,21 @@
# cojson-storage-sqlite
## 0.11.5
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
- cojson-storage@0.11.5
## 0.11.4
### Patch Changes
- Updated dependencies [7f036c1]
- cojson@0.11.4
- cojson-storage@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-storage-rn-sqlite",
"type": "module",
"version": "0.11.3",
"version": "0.11.5",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,5 +1,21 @@
# cojson-storage-sqlite
## 0.11.5
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
- cojson-storage@0.11.5
## 0.11.4
### Patch Changes
- Updated dependencies [7f036c1]
- cojson@0.11.4
- cojson-storage@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,13 +1,13 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.11.3",
"version": "0.11.5",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^11.7.0",
"cojson": "workspace:0.11.3",
"cojson": "workspace:0.11.5",
"cojson-storage": "workspace:*"
},
"devDependencies": {

View File

@@ -1,5 +1,19 @@
# cojson-storage
## 0.11.5
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
## 0.11.4
### Patch Changes
- Updated dependencies [7f036c1]
- cojson@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage",
"version": "0.11.3",
"version": "0.11.5",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,19 @@
# cojson-transport-nodejs-ws
## 0.11.5
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
## 0.11.4
### Patch Changes
- Updated dependencies [7f036c1]
- cojson@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.11.3",
"version": "0.11.5",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,4 +1,4 @@
import { ControlledAgent, LocalNode } from "cojson";
import { ControlledAgent, type CryptoProvider, LocalNode } from "cojson";
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { WebSocket } from "ws";
@@ -8,7 +8,7 @@ import { startSyncServer } from "./syncServer";
describe("WebSocket Peer Integration", () => {
let server: any;
let syncServerUrl: string;
let crypto: WasmCrypto;
let crypto: CryptoProvider;
beforeEach(async () => {
crypto = await WasmCrypto.create();

View File

@@ -1,5 +1,17 @@
# cojson
## 0.11.5
### Patch Changes
- 60f5b3f: Downgrade the WasmCrypto initialization error logging to a warning
## 0.11.4
### Patch Changes
- 7f036c1: Use PureJSCrypto as fallback when WasmCrypto fails to initialize
## 0.11.3
### Patch Changes

View File

@@ -25,7 +25,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.11.3",
"version": "0.11.5",
"devDependencies": {
"@opentelemetry/sdk-metrics": "^1.29.0",
"typescript": "~5.6.2",

View File

@@ -20,6 +20,7 @@ import { RawCoID, TransactionID } from "../ids.js";
import { Stringified, stableStringify } from "../jsonStringify.js";
import { JsonValue } from "../jsonValue.js";
import { logger } from "../logger.js";
import { PureJSCrypto } from "./PureJSCrypto.js";
import {
CryptoProvider,
Encrypted,
@@ -49,8 +50,17 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
super();
}
static async create(): Promise<WasmCrypto> {
await initialize();
static async create(): Promise<WasmCrypto | PureJSCrypto> {
try {
await initialize();
} catch (e) {
logger.warn(
"Failed to initialize WasmCrypto, falling back to PureJSCrypto",
{ err: e },
);
return new PureJSCrypto();
}
return new WasmCrypto();
}

View File

@@ -1,5 +1,26 @@
# jazz-browser-media-images
## 0.11.5
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
- jazz-browser@0.11.5
- jazz-tools@0.11.5
## 0.11.4
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- Updated dependencies [7f036c1]
- jazz-tools@0.11.4
- cojson@0.11.4
- jazz-browser@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,14 +1,14 @@
{
"name": "jazz-auth-clerk",
"version": "0.11.3",
"version": "0.11.5",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.11.3",
"jazz-browser": "workspace:0.11.3",
"jazz-tools": "workspace:0.11.3"
"cojson": "workspace:0.11.5",
"jazz-browser": "workspace:0.11.5",
"jazz-tools": "workspace:0.11.5"
},
"scripts": {
"format-and-lint": "biome check .",

View File

@@ -1,5 +1,22 @@
# jazz-browser-media-images
## 0.11.5
### Patch Changes
- jazz-browser@0.11.5
- jazz-tools@0.11.5
## 0.11.4
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.11.3",
"version": "0.11.5",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -8,8 +8,8 @@
"dependencies": {
"@types/image-blob-reduce": "^4.1.1",
"image-blob-reduce": "^4.1.0",
"jazz-browser": "workspace:0.11.3",
"jazz-tools": "workspace:0.11.3",
"jazz-browser": "workspace:0.11.5",
"jazz-tools": "workspace:0.11.5",
"pica": "^9.0.1",
"typescript": "~5.6.2"
},

View File

@@ -1,5 +1,28 @@
# jazz-browser
## 0.11.5
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
- cojson-storage-indexeddb@0.11.5
- cojson-transport-ws@0.11.5
- jazz-tools@0.11.5
## 0.11.4
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- Updated dependencies [7f036c1]
- jazz-tools@0.11.4
- cojson@0.11.4
- cojson-storage-indexeddb@0.11.4
- cojson-transport-ws@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser",
"version": "0.11.3",
"version": "0.11.5",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,28 @@
# jazz-inspector
## 0.11.5
### Patch Changes
- Updated dependencies [60f5b3f]
- cojson@0.11.5
- jazz-react-core@0.11.5
- jazz-tools@0.11.5
## 0.11.4
### Patch Changes
- d133d47: add z-index of 1 to inspector
- e902405: add position prop to JazzInspector component
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- Updated dependencies [7f036c1]
- jazz-tools@0.11.4
- cojson@0.11.4
- jazz-react-core@0.11.4
## 0.11.3
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-inspector",
"version": "0.11.3",
"version": "0.11.5",
"type": "module",
"main": "./dist/app.js",
"types": "./dist/app.d.ts",
@@ -16,6 +16,9 @@
"preview": "vite preview"
},
"dependencies": {
"@twind/core": "^1.1.3",
"@twind/preset-autoprefix": "^1.0.7",
"@twind/preset-tailwind": "^1.1.4",
"cojson": "workspace:*",
"jazz-react-core": "workspace:*",
"jazz-tools": "workspace:*"

View File

@@ -1 +1,4 @@
// Import Twind setup
import "./twind.js";
export { JazzInspector } from "./viewer/new-app.js";

View File

@@ -0,0 +1,54 @@
import { defineConfig } from "@twind/core";
import presetAutoprefix from "@twind/preset-autoprefix";
import presetTailwind from "@twind/preset-tailwind";
const stonePalette = {
50: "oklch(0.988281 0.002 75)",
100: "oklch(0.980563 0.002 75)",
200: "oklch(0.917969 0.002 75)",
300: "oklch(0.853516 0.002 75)",
400: "oklch(0.789063 0.002 75)",
500: "oklch(0.726563 0.002 75)",
600: "oklch(0.613281 0.002 75)",
700: "oklch(0.523438 0.002 75)",
800: "oklch(0.412109 0.002 75)",
900: "oklch(0.302734 0.002 75)",
925: "oklch(0.220000 0.002 75)",
950: "oklch(0.193359 0.002 75)",
};
const stonePaletteWithAlpha = { ...stonePalette };
Object.keys(stonePalette).forEach((key) => {
// @ts-ignore
stonePaletteWithAlpha[key] = stonePaletteWithAlpha[key].replace(
")",
"/ <alpha-value>)",
);
});
export default defineConfig({
presets: [presetAutoprefix(), presetTailwind()],
theme: {
extend: {
colors: {
stone: stonePaletteWithAlpha,
gray: stonePaletteWithAlpha,
blue: {
50: "#f5f7ff",
100: "#ebf0fe",
200: "#d6e0fd",
300: "#b3c7fc",
400: "#8aa6f9",
500: "#5870F1",
600: "#3651E7",
700: "#3313F7",
800: "#2A12BE",
900: "#12046A",
950: "#1e1b4b",
DEFAULT: "#3313F7",
},
},
},
},
});

View File

@@ -0,0 +1,8 @@
import { install, tw } from "@twind/core";
import config from "./twind.config";
// Install Twind globally
install(config);
// Export the tw function for use in components
export { tw };

View File

@@ -11,41 +11,10 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
onBreadcrumbClick,
}) => {
return (
<div
style={{
position: "relative",
zIndex: 20,
backgroundColor: "rgba(129, 140, 248, 0.1)", // indigo-400/10 equivalent
backdropFilter: "blur(4px)",
borderRadius: "0.5rem",
display: "inline-flex",
paddingLeft: "0.5rem",
paddingRight: "0.5rem",
paddingTop: "0.25rem",
paddingBottom: "0.25rem",
whiteSpace: "pre",
transition: "all",
alignItems: "center",
gap: "0.25rem",
minHeight: "2.5rem",
}}
>
<div className="relative z-20 bg-blue-400/10 backdrop-blur-sm rounded-lg inline-flex px-2 py-1 whitespace-pre transition-all items-center gap-1 min-h-[2.5rem]">
<button
onClick={() => onBreadcrumbClick(-1)}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "0.25rem",
borderRadius: "0.125rem",
transition: "colors",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor = "rgba(99, 102, 241, 0.1)")
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "transparent")
}
className="flex items-center justify-center p-1 rounded-sm transition-colors"
aria-label="Go to home"
>
Start
@@ -54,26 +23,16 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
return (
<span
key={index}
style={{
display: "inline-block",
paddingLeft: index === 0 ? "0.25rem" : "0",
paddingRight: index === path.length - 1 ? "0.25rem" : "0",
}}
className={`inline-block ${index === 0 ? "pl-1" : "pl-0"} ${
index === path.length - 1 ? "pr-1" : "pr-0"
}`}
>
{index === 0 ? null : (
<span style={{ color: "rgba(99, 102, 241, 0.3)" }}>{" / "}</span>
<span className="text-blue-600/30">{" / "}</span>
)}
<button
onClick={() => onBreadcrumbClick(index)}
style={{
color: "rgb(67, 56, 202)",
}}
onMouseOver={(e) =>
(e.currentTarget.style.textDecoration = "underline")
}
onMouseOut={(e) =>
(e.currentTarget.style.textDecoration = "none")
}
className="text-blue hover:underline dark:text-blue-400"
>
{index === 0 ? page.name || "Root" : page.name}
</button>

View File

@@ -161,7 +161,7 @@ const LabelContentPair = ({
content: React.ReactNode;
}) => {
return (
<div style={{ display: "flex", flexDirection: "column", gap: "0.375rem" }}>
<div className="flex flex-col gap-1.5">
<span>{label}</span>
<span>{content}</span>
</div>
@@ -219,34 +219,12 @@ function RenderCoBinaryStream({
const sizeInKB = (file.totalSize || 0) / 1024;
return (
<div
style={{
marginTop: "2rem",
display: "flex",
flexDirection: "column",
gap: "2rem",
}}
>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gap: "0.5rem",
maxWidth: "48rem",
}}
>
<div className="mt-8 flex flex-col gap-8">
<div className="grid grid-cols-3 gap-2 max-w-3xl">
<LabelContentPair
label="Mime Type"
content={
<span
style={{
fontFamily: "monospace",
backgroundColor: "rgb(243 244 246)",
borderRadius: "0.25rem",
padding: "0.25rem 0.5rem",
fontSize: "0.875rem",
}}
>
<span className="font-mono bg-gray-100 rounded px-2 py-1 text-sm dark:bg-stone-900">
{mimeType || "No mime type"}
</span>
}
@@ -275,13 +253,7 @@ function RenderCoBinaryStream({
<LabelContentPair
label="Preview"
content={
<div
style={{
backgroundColor: "rgb(249 250 251)",
padding: "0.75rem",
borderRadius: "0.125rem",
}}
>
<div className="bg-gray-50 dark:bg-gray-925 p-3 rounded">
<RenderBlobImage blob={blob} />
</div>
}
@@ -302,30 +274,10 @@ function RenderCoStream({
const userCoIds = streamPerUser.map((stream) => stream.split("_session")[0]);
return (
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gap: "0.5rem",
}}
>
<div className="grid grid-cols-3 gap-2">
{userCoIds.map((id, idx) => (
<div
style={{
padding: "0.75rem",
borderRadius: "0.5rem",
overflow: "hidden",
backgroundColor: "white",
border: "1px solid #e5e7eb",
cursor: "pointer",
boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
transition: "background-color 0.2s",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor =
"rgba(243, 244, 246, 0.05)")
}
onMouseOut={(e) => (e.currentTarget.style.backgroundColor = "white")}
className="p-3 rounded-lg overflow-hidden border border-gray-200 cursor-pointer shadow-sm hover:bg-gray-100/5"
key={id}
>
<AccountOrGroupPreview coId={id as CoID<RawCoValue>} node={node} />

View File

@@ -16,66 +16,26 @@ export function GridView({
const entries = Object.entries(data);
return (
<div
style={{
display: "grid",
gridTemplateColumns: "1fr",
gap: "1rem",
padding: "0.5rem",
}}
>
<div className="grid grid-cols-1 gap-4 p-2">
{entries.map(([key, child], childIndex) => (
<div
key={childIndex}
style={{
padding: "0.75rem",
borderRadius: "0.5rem",
overflow: "hidden",
transition: "background-color 0.2s",
...(isCoId(child)
? {
backgroundColor: "white",
border: "1px solid #e5e7eb",
cursor: "pointer",
boxShadow: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
":hover": {
backgroundColor: "rgba(243, 244, 246, 0.05)",
},
}
: {
backgroundColor: "rgb(249, 250, 251)",
}),
}}
className={`p-3 rounded-lg overflow-hidden transition-colors ${
isCoId(child)
? " border border-gray-200 cursor-pointer shadow-sm hover:bg-gray-100/5"
: "bg-gray-50 dark:bg-gray-925"
}`}
onClick={() =>
isCoId(child) &&
onNavigate([{ coId: child as CoID<RawCoValue>, name: key }])
}
>
<h3
style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
<h3 className="overflow-hidden text-ellipsis whitespace-nowrap">
{isCoId(child) ? (
<span
style={{
fontWeight: 500,
display: "flex",
justifyContent: "space-between",
}}
>
<span className="font-medium flex justify-between">
{key}
<div
style={{
padding: "0.25rem 0.5rem",
fontSize: "0.75rem",
backgroundColor: "rgb(243, 244, 246)",
borderRadius: "0.25rem",
}}
>
<div className="py-1 px-2 text-xs bg-gray-100 rounded dark:bg-gray-900">
<ResolveIcon coId={child as CoID<RawCoValue>} node={node} />
</div>
</span>
@@ -83,7 +43,7 @@ export function GridView({
<span>{key}</span>
)}
</h3>
<div style={{ marginTop: "0.5rem", fontSize: "0.875rem" }}>
<div className="mt-2 text-sm">
{isCoId(child) ? (
<CoMapPreview coId={child as CoID<RawCoValue>} node={node} />
) : (

View File

@@ -13,10 +13,7 @@ type Position =
| "right"
| "left";
export function JazzInspector({
position = "right",
hidden,
}: { position?: Position; hidden?: boolean }) {
export function JazzInspector({ position = "right" }: { position?: Position }) {
const [open, setOpen] = useState(false);
const [coValueId, setCoValueId] = useState<CoID<RawCoValue> | "">("");
const { path, addPages, goToIndex, goBack, setPage } = usePagePath();
@@ -24,68 +21,29 @@ export function JazzInspector({
const { me } = useAccount();
const localNode = me._raw.core.node;
if (hidden) return;
const handleCoValueIdSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (coValueId) {
setPage(coValueId);
}
setCoValueId("");
};
const positionStyles = {
"bottom right": {
bottom: 0,
right: 0,
},
"bottom left": {
bottom: 0,
left: 0,
},
"top right": {
top: 0,
right: 0,
},
"top left": {
top: 0,
left: 0,
},
right: {
right: 0,
top: "50%",
transform: "translateY(-50%)",
},
left: {
left: 0,
top: "50%",
transform: "translateY(-50%)",
},
const positionClasses = {
"bottom right": "bottom-0 right-0",
"bottom left": "bottom-0 left-0",
"top right": "top-0 right-0",
"top left": "top-0 left-0",
right: "right-0 top-1/2 -translate-y-1/2",
left: "left-0 top-1/2 -translate-y-1/2",
};
if (!open) {
return (
<button
onClick={() => setOpen(true)}
style={{
position: "fixed",
margin: "1rem",
backgroundColor: "white",
border: "1px solid #e5e7eb",
borderRadius: "0.5rem",
padding: "0.5rem",
boxShadow:
"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
...positionStyles[position],
}}
className={`fixed w-10 h-10 inline-block bottom-0 right-0 m-4 bg-white border rounded-md shadow-md p-1.5 ${positionClasses[position]}`}
>
<svg
style={{
width: "1.5rem",
height: "1.5rem",
position: "relative",
left: "-1px",
top: "1px",
}}
className="w-full h-auto relative -left-px text-blue"
xmlns="http://www.w3.org/2000/svg"
width="119"
height="115"
@@ -96,35 +54,39 @@ export function JazzInspector({
fillRule="evenodd"
clipRule="evenodd"
d="M118.179 23.8277V0.167999C99.931 7.5527 79.9854 11.6192 59.0897 11.6192C47.1466 11.6192 35.5138 10.2908 24.331 7.7737V30.4076V60.1508C23.2955 59.4385 22.1568 58.8458 20.9405 58.3915C18.1732 57.358 15.128 57.0876 12.1902 57.6145C9.2524 58.1414 6.5539 59.4419 4.4358 61.3516C2.3178 63.2613 0.875401 65.6944 0.291001 68.3433C-0.293399 70.9921 0.00659978 73.7377 1.1528 76.2329C2.2991 78.728 4.2403 80.861 6.7308 82.361C9.2214 83.862 12.1495 84.662 15.1448 84.662C15.6054 84.662 15.8365 84.662 16.0314 84.659C26.5583 84.449 35.042 75.9656 35.2513 65.4386C35.2534 65.3306 35.2544 65.2116 35.2548 65.0486L35.2552 64.7149V64.5521V61.0762V32.1993C43.0533 33.2324 51.0092 33.7656 59.0897 33.7656C59.6696 33.7656 60.2489 33.7629 60.8276 33.7574V89.696C59.792 88.983 58.6533 88.391 57.437 87.936C54.6697 86.903 51.6246 86.632 48.6867 87.159C45.7489 87.686 43.0504 88.987 40.9323 90.896C38.8143 92.806 37.3719 95.239 36.7875 97.888C36.2032 100.537 36.5031 103.283 37.6494 105.778C38.7956 108.273 40.7368 110.405 43.2273 111.906C45.7179 113.406 48.646 114.207 51.6414 114.207C52.1024 114.207 52.3329 114.207 52.5279 114.203C63.0548 113.994 71.5385 105.51 71.7478 94.983C71.7517 94.788 71.7517 94.558 71.7517 94.097V90.621V33.3266C83.962 32.4768 95.837 30.4075 107.255 27.2397V59.9017C106.219 59.1894 105.081 58.5966 103.864 58.1424C101.097 57.1089 98.052 56.8384 95.114 57.3653C92.176 57.8922 89.478 59.1927 87.36 61.1025C85.242 63.0122 83.799 65.4453 83.215 68.0941C82.631 70.743 82.931 73.4886 84.077 75.9837C85.223 78.4789 87.164 80.612 89.655 82.112C92.145 83.612 95.073 84.413 98.069 84.413C98.53 84.413 98.76 84.413 98.955 84.409C109.482 84.2 117.966 75.7164 118.175 65.1895C118.179 64.9945 118.179 64.764 118.179 64.3029V60.8271V23.8277Z"
fill="#3313F7"
fill="currentColor"
/>
</svg>
<span className="sr-only">Open Jazz Inspector</span>
</button>
);
}
return (
<div
style={{
position: "fixed",
bottom: 0,
left: 0,
width: "100%",
backgroundColor: "white",
borderTop: "1px solid #e5e7eb",
padding: "1rem",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "1rem",
}}
>
<div className="fixed bottom-0 left-0 w-full bg-white border-t border-gray-200 p-4 dark:border-stone-900 dark:bg-stone-925">
<div className="flex items-center gap-4 mb-4">
<Breadcrumbs path={path} onBreadcrumbClick={goToIndex} />
<button onClick={() => setOpen(false)}>Close</button>
<div className="flex-1">
<form onSubmit={handleCoValueIdSubmit}>
{path.length !== 0 && (
<input
className="border p-2 rounded-lg min-w-[21rem] font-mono"
placeholder="co_z1234567890abcdef123456789"
value={coValueId}
onChange={(e) =>
setCoValueId(e.target.value as CoID<RawCoValue>)
}
/>
)}
</form>
</div>
<button
className="ml-auto"
type="button"
onClick={() => setOpen(false)}
>
Close
</button>
</div>
<PageStack
@@ -136,80 +98,32 @@ export function JazzInspector({
<form
onSubmit={handleCoValueIdSubmit}
aria-hidden={path.length !== 0}
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
gap: "0.5rem",
height: "100%",
width: "100%",
marginBottom: "5rem",
transition: "all 150ms",
opacity: path.length > 0 ? 0 : 1,
transform:
path.length > 0 ? "translateY(-0.5rem) scale(0.95)" : "none",
}}
className={`flex flex-col justify-center items-center gap-2 h-full w-full mb-20 transition-all duration-150 ${
path.length > 0
? "opacity-0 translate-y-[-0.5rem] scale-95"
: "opacity-100"
}`}
>
<fieldset
style={{
display: "flex",
flexDirection: "column",
gap: "0.5rem",
fontSize: "0.875rem",
}}
>
<h2
style={{
fontSize: "1.875rem",
fontWeight: 500,
color: "#030712",
textAlign: "center",
marginBottom: "1rem",
}}
>
<fieldset className="flex flex-col gap-2 text-sm">
<h2 className="text-lg font-medium mb-4 text-stone-900 dark:text-white">
Jazz CoValue Inspector
</h2>
<input
style={{
border: "1px solid #e5e7eb",
padding: "1rem",
borderRadius: "0.5rem",
minWidth: "21rem",
fontFamily: "monospace",
}}
className="border border-gray-200 p-4 rounded-lg min-w-[21rem] font-mono"
placeholder="co_z1234567890abcdef123456789"
value={coValueId}
onChange={(e) => setCoValueId(e.target.value as CoID<RawCoValue>)}
/>
<button
type="submit"
style={{
backgroundColor: "rgb(99 102 241)",
color: "white",
padding: "0.5rem 1rem",
borderRadius: "0.375rem",
}}
onMouseOver={(e) =>
(e.currentTarget.style.backgroundColor =
"rgba(99 102 241, 0.8)")
}
onMouseOut={(e) =>
(e.currentTarget.style.backgroundColor = "rgb(99 102 241)")
}
className="bg-blue text-white py-2 px-4 rounded-md hover:bg-blue-800"
>
Inspect
</button>
<hr />
<button
type="button"
style={{
border: "1px solid #e5e7eb",
display: "inline-block",
padding: "0.375rem 0.5rem",
color: "black",
borderRadius: "0.375rem",
}}
className="border border-gray-200 inline-block py-1.5 px-2 rounded-md"
onClick={() => {
setCoValueId(me._raw.id);
setPage(me._raw.id);

View File

@@ -27,28 +27,8 @@ export function PageStack({
const index = path.length - 1;
return (
<div
style={{
position: "relative",
marginTop: "1rem",
height: "40vh",
overflowY: "auto",
}}
>
{children && (
<div
style={{
position: "absolute",
top: 0,
right: 0,
bottom: 0,
left: 0,
paddingBottom: "5rem",
}}
>
{children}
</div>
)}
<div className="relative mt-4 h-[40vh] overflow-y-auto">
{children && <div className="absolute inset-0 pb-20">{children}</div>}
{node && page && (
<Page
coId={page.coId}
@@ -57,15 +37,13 @@ export function PageStack({
onHeaderClick={goBack}
onNavigate={addPages}
isTopLevel={index === path.length - 1}
className="transition-transform transition-opacity duration-300 ease-out"
style={{
transform: `translateZ(${(index - path.length + 1) * 200}px) scale(${
1 - (path.length - index - 1) * 0.05
}) translateY(${-(index - path.length + 1) * -4}%)`,
opacity: 1 - (path.length - index - 1) * 0.05,
zIndex: index,
transitionProperty: "transform, opacity",
transitionDuration: "0.3s",
transitionTimingFunction: "ease-out",
}}
/>
)}

View File

@@ -16,6 +16,7 @@ type PageProps = {
onHeaderClick?: () => void;
isTopLevel?: boolean;
style: React.CSSProperties;
className?: string;
};
export function Page({
@@ -25,6 +26,7 @@ export function Page({
onNavigate,
onHeaderClick,
style,
className = "",
isTopLevel,
}: PageProps) {
const { value, snapshot, type, extendedType } = useResolvedCoValue(
@@ -52,31 +54,15 @@ export function Page({
return (
<div
style={{
position: "absolute",
zIndex: 1,
inset: 0,
backgroundColor: "white",
borderWidth: "1px",
borderColor: "rgba(0, 0, 0, 0.05)",
borderRadius: "0.75rem",
boxShadow:
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
padding: "1.5rem",
width: "100%",
height: "100%",
backgroundClip: "padding-box",
}}
style={style}
className={
className +
" absolute z-10 inset-0 border rounded-xl shadow-lg p-6 w-full h-full bg-clip-padding"
}
>
{!isTopLevel && (
<div
style={{
position: "absolute",
left: 0,
right: 0,
top: 0,
height: "2.5rem",
}}
className="absolute left-0 right-0 top-0 h-10"
aria-label="Back"
onClick={() => {
onHeaderClick?.();
@@ -84,72 +70,30 @@ export function Page({
aria-hidden="true"
></div>
)}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "1rem",
}}
>
<div
style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}
>
<h2
style={{
fontSize: "1.5rem",
fontWeight: "700",
display: "flex",
alignItems: "flex-start",
flexDirection: "column",
gap: "0.25rem",
}}
>
<div className="flex justify-between items-center mb-4">
<div className="flex flex-col gap-2">
<h2 className="text-2xl font-bold flex flex-col items-start gap-1">
<span>
{name}
{typeof snapshot === "object" && "name" in snapshot ? (
<span style={{ color: "rgb(75, 85, 99)", fontWeight: "500" }}>
<span className="text-gray-600 font-medium">
{" "}
{(snapshot as { name: string }).name}
</span>
) : null}
</span>
</h2>
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
<span
style={{
fontSize: "0.75rem",
color: "rgb(55, 65, 81)",
fontWeight: "500",
padding: "0.125rem 0.25rem",
marginLeft: "-0.125rem",
borderRadius: "0.25rem",
backgroundColor: "rgba(55, 65, 81, 0.05)",
display: "inline-block",
fontFamily: "monospace",
}}
>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
{type && <TypeIcon type={type} extendedType={extendedType} />}
</span>
<span
style={{
fontSize: "0.75rem",
color: "rgb(55, 65, 81)",
fontWeight: "500",
padding: "0.125rem 0.25rem",
marginLeft: "-0.125rem",
borderRadius: "0.25rem",
backgroundColor: "rgba(55, 65, 81, 0.05)",
display: "inline-block",
fontFamily: "monospace",
}}
>
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
{coId}
</span>
</div>
</div>
</div>
<div style={{ overflow: "auto", maxHeight: "calc(100% - 4rem)" }}>
<div className="overflow-auto max-h-[calc(100%-4rem)]">
{type === "costream" ? (
<CoStreamView
data={snapshot}
@@ -163,13 +107,7 @@ export function Page({
<TableView data={snapshot} node={node} onNavigate={onNavigate} />
)}
{extendedType !== "account" && extendedType !== "group" && (
<div
style={{
fontSize: "0.75rem",
color: "rgb(107, 114, 128)",
marginTop: "1rem",
}}
>
<div className="text-xs text-gray-500 mt-4">
Owned by{" "}
<AccountOrGroupPreview
coId={value.group.id}

View File

@@ -51,45 +51,23 @@ export function TableView({
return (
<div>
<table
style={{
minWidth: "100%",
borderSpacing: 0,
borderCollapse: "collapse",
}}
>
<thead
style={{
position: "sticky",
top: 0,
borderBottom: "1px solid #e5e7eb",
}}
>
<table className="min-w-full border-spacing-0 border-collapse">
<thead className="sticky top-0 border-b border-gray-200">
<tr>
{["", ...keys].map((key) => (
<th
key={key}
style={{
padding: "0.75rem 1rem",
backgroundColor: "#f9fafb",
textAlign: "left",
fontSize: "0.75rem",
fontWeight: 500,
color: "#6b7280",
borderRadius: "0.25rem",
}}
className="p-3 bg-gray-50 dark:bg-gray-925 text-left text-xs font-medium text-gray-500 rounded"
>
{key}
</th>
))}
</tr>
</thead>
<tbody
style={{ backgroundColor: "white", borderTop: "1px solid #e5e7eb" }}
>
<tbody className=" border-t border-gray-200">
{resolvedRows.slice(0, visibleRowsCount).map((item, index) => (
<tr key={index}>
<td style={{ padding: "0.25rem 0.25rem" }}>
<td className="p-1">
<button
onClick={() =>
onNavigate([
@@ -99,21 +77,7 @@ export function TableView({
},
])
}
style={{
padding: "1rem",
whiteSpace: "nowrap",
fontSize: "0.875rem",
color: "#6b7280",
borderRadius: "0.25rem",
}}
onMouseOver={(e) => {
e.currentTarget.style.backgroundColor = "#f3f4f6";
e.currentTarget.style.color = "#3b82f6";
}}
onMouseOut={(e) => {
e.currentTarget.style.backgroundColor = "transparent";
e.currentTarget.style.color = "#6b7280";
}}
className="p-4 whitespace-nowrap text-sm text-gray-500 rounded hover:bg-gray-100 hover:text-blue-500"
>
<LinkIcon />
</button>
@@ -121,12 +85,7 @@ export function TableView({
{keys.map((key) => (
<td
key={key}
style={{
padding: "1rem",
whiteSpace: "nowrap",
fontSize: "0.875rem",
color: "#6b7280",
}}
className="p-4 whitespace-nowrap text-sm text-gray-500"
>
<ValueRenderer
json={(item.snapshot as JsonObject)[key]}
@@ -153,38 +112,18 @@ export function TableView({
))}
</tbody>
</table>
<div
style={{
padding: "1rem 0",
color: "#6b7280",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "0.5rem",
}}
>
<div className="py-4 text-gray-500 flex items-center justify-between gap-2">
<span>
Showing {Math.min(visibleRowsCount, coIdArray.length)} of{" "}
{coIdArray.length}
</span>
{hasMore && (
<div style={{ textAlign: "center" }}>
<div className="text-center">
<button
onClick={loadMore}
style={{
padding: "0.5rem 1rem",
backgroundColor: "#3b82f6",
color: "white",
borderRadius: "0.25rem",
}}
onMouseOver={(e) => {
e.currentTarget.style.backgroundColor = "#2563eb";
}}
onMouseOut={(e) => {
e.currentTarget.style.backgroundColor = "#3b82f6";
}}
className="px-4 py-2 bg-blue text-white rounded hover:bg-blue-800"
>
Load More
Load more
</button>
</div>
)}

View File

@@ -25,7 +25,7 @@ export const TypeIcon = ({
const iconKey = extendedType || type;
const icon = iconMap[iconKey as keyof typeof iconMap];
return icon ? <span style={{ fontFamily: "monospace" }}>{icon}</span> : null;
return icon ? <span className="font-mono">{icon}</span> : null;
};
export const ResolveIcon = ({
@@ -38,17 +38,10 @@ export const ResolveIcon = ({
const { type, extendedType, snapshot } = useResolvedCoValue(coId, node);
if (snapshot === "unavailable" && !type) {
return <div style={{ color: "#4B5563", fontWeight: 500 }}>Unavailable</div>;
return <div className="text-gray-600 font-medium">Unavailable</div>;
}
if (!type)
return (
<div
style={{ whiteSpace: "pre", width: "3.5rem", fontFamily: "monospace" }}
>
{" "}
</div>
);
if (!type) return <div className="whitespace-pre w-14 font-mono"> </div>;
return <TypeIcon type={type} extendedType={extendedType} />;
};

View File

@@ -17,32 +17,24 @@ export function ValueRenderer({
compact?: boolean;
onCoIDClick?: (childNode: CoID<RawCoValue>) => void;
}) {
const [isExpanded, setIsExpanded] = useState(false);
if (typeof json === "undefined" || json === undefined) {
return <span style={{ color: "#9CA3AF" }}>undefined</span>;
return <span className="text-gray-400">undefined</span>;
}
if (json === null) {
return <span style={{ color: "#9CA3AF" }}>null</span>;
return <span className="text-gray-400">null</span>;
}
if (typeof json === "string" && json.startsWith("co_")) {
const linkStyle = onCoIDClick
? {
color: "#3B82F6",
cursor: "pointer",
display: "inline-flex",
gap: "0.25rem",
alignItems: "center",
}
: {
display: "inline-flex",
gap: "0.25rem",
alignItems: "center",
};
const linkClasses = onCoIDClick
? "text-blue-500 cursor-pointer inline-flex gap-1 items-center"
: "inline-flex gap-1 items-center";
return (
<span
style={linkStyle}
className={linkClasses}
onClick={() => {
onCoIDClick?.(json as CoID<RawCoValue>);
}}
@@ -55,33 +47,22 @@ export function ValueRenderer({
if (typeof json === "string") {
return (
<span style={{ color: "#064E3B", fontFamily: "monospace" }}>{json}</span>
<span className="text-teal-900 font-mono dark:text-teal-200">{json}</span>
);
}
if (typeof json === "number") {
return <span style={{ color: "#A855F7" }}>{json}</span>;
return <span className="text-purple-500 dark:text-purple-200">{json}</span>;
}
if (typeof json === "boolean") {
const booleanStyle = {
color: json ? "#15803D" : "#B45309",
backgroundColor: json
? "rgba(34, 197, 94, 0.05)"
: "rgba(245, 158, 11, 0.05)",
fontFamily: "monospace",
display: "inline-block",
padding: "0.125rem 0.25rem",
borderRadius: "0.25rem",
};
return <span style={booleanStyle}>{json.toString()}</span>;
}
if (Array.isArray(json)) {
return (
<span title={JSON.stringify(json)}>
Array <span style={{ color: "#6B7280" }}>({json.length})</span>
<span
className={`inline-block py-0.5 px-1 rounded ${
json ? "text-green-700 bg-green-50" : "text-amber-700 bg-amber-50"
} font-mono`}
>
{json.toString()}
</span>
);
}
@@ -90,23 +71,31 @@ export function ValueRenderer({
return (
<span
title={JSON.stringify(json, null, 2)}
style={{
display: "inline-block",
maxWidth: "16rem",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
className="inline-block max-w-64"
>
{compact ? (
<span>
Object{" "}
<span style={{ color: "#6B7280" }}>
({Object.keys(json).length})
</span>
<span className="text-gray-500">({Object.keys(json).length})</span>
<pre className="mt-1 text-sm whitespace-pre-wrap">
{isExpanded
? JSON.stringify(json, null, 2)
: JSON.stringify(json, null, 2)
.split("\n")
.slice(0, 3)
.join("\n") + (Object.keys(json).length > 2 ? "\n..." : "")}
</pre>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-xs text-gray-500 hover:text-gray-700"
>
{isExpanded ? "Show less" : "Show more"}
</button>
</span>
) : (
JSON.stringify(json, null, 2)
<pre className="whitespace-pre-wrap">
{JSON.stringify(json, null, 2)}
</pre>
)}
</span>
);
@@ -131,22 +120,14 @@ export const CoMapPreview = ({
if (!snapshot) {
return (
<div
style={{
borderRadius: "0.25rem",
backgroundColor: "#F3F4F6",
animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
whiteSpace: "pre",
width: "6rem",
}}
>
<div className="rounded bg-gray-100 animate-pulse whitespace-pre w-24">
{" "}
</div>
);
}
if (snapshot === "unavailable" && !value) {
return <div style={{ color: "#6B7280" }}>Unavailable</div>;
return <div className="text-gray-500">Unavailable</div>;
}
if (extendedType === "image" && isBrowserImage(snapshot)) {
@@ -154,16 +135,9 @@ export const CoMapPreview = ({
<div>
<img
src={snapshot.placeholderDataURL}
style={{
width: "2rem",
height: "2rem",
border: "2px solid white",
boxShadow:
"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
margin: "0.5rem 0",
}}
className="w-8 h-8 border-2 border-white shadow my-2"
/>
<span style={{ color: "#6B7280", fontSize: "0.875rem" }}>
<span className="text-gray-500 text-sm">
{snapshot.originalSize[0]} x {snapshot.originalSize[1]}
</span>
</div>
@@ -174,9 +148,7 @@ export const CoMapPreview = ({
return (
<div>
Record{" "}
<span style={{ color: "#6B7280" }}>
({Object.keys(snapshot).length})
</span>
<span className="text-gray-500">({Object.keys(snapshot).length})</span>
</div>
);
}
@@ -185,7 +157,7 @@ export const CoMapPreview = ({
return (
<div>
List{" "}
<span style={{ color: "#6B7280" }}>
<span className="text-gray-500">
({(snapshot as unknown as []).length})
</span>
</div>
@@ -193,19 +165,13 @@ export const CoMapPreview = ({
}
return (
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
<div
style={{
display: "grid",
gridTemplateColumns: "auto 1fr",
gap: "0.5rem",
}}
>
<div className="flex flex-col gap-2">
<div className="grid grid-cols-[auto_1fr] gap-2">
{Object.entries(snapshot)
.slice(0, limit)
.map(([key, value]) => (
<React.Fragment key={key}>
<span style={{ fontWeight: "bold" }}>{key}: </span>
<span className="font-bold">{key}: </span>
<span>
<ValueRenderer json={value} />
</span>
@@ -213,9 +179,7 @@ export const CoMapPreview = ({
))}
</div>
{Object.entries(snapshot).length > limit && (
<div
style={{ textAlign: "left", fontSize: "0.875rem", color: "#6B7280" }}
>
<div className="text-left text-sm text-gray-500">
{Object.entries(snapshot).length - limit} more
</div>
)}
@@ -262,18 +226,13 @@ export function AccountOrGroupPreview({
const displayName = extendedType === "account" ? name || "Account" : "Group";
const displayText = showId ? `${displayName} (${coId})` : displayName;
const props = onClick
? {
onClick: () => onClick(displayName),
style: {
color: "#3B82F6",
cursor: "pointer",
textDecoration: "underline",
},
}
: {
style: { color: "#6B7280" },
};
const className = onClick
? "text-blue cursor-pointer underline dark:text-blue-400"
: "text-gray-500";
return <span {...props}>{displayText}</span>;
return (
<span className={className} onClick={() => onClick?.(displayName)}>
{displayText}
</span>
);
}

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