Compare commits
50 Commits
feat/hide-
...
1572-files
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4e342a59b | ||
|
|
73b71df524 | ||
|
|
fcaa5dddda | ||
|
|
c27fadc4e0 | ||
|
|
56af3c7412 | ||
|
|
5e5ef10675 | ||
|
|
2efc55f9bf | ||
|
|
7101f10573 | ||
|
|
3849392272 | ||
|
|
ce1fc720c1 | ||
|
|
4eb634175d | ||
|
|
adc6343531 | ||
|
|
c745e099fe | ||
|
|
970c9f5c6c | ||
|
|
4e3986ae98 | ||
|
|
6c691bf641 | ||
|
|
d03ba7fdd0 | ||
|
|
35cb7d8988 | ||
|
|
0e40a9daab | ||
|
|
bd1996c458 | ||
|
|
373ebec04c | ||
|
|
7fedbeb8e4 | ||
|
|
4bfc23091c | ||
|
|
6e5ea6f19c | ||
|
|
bcd67cb23f | ||
|
|
27781ed778 | ||
|
|
eb097ecdb1 | ||
|
|
cdae2603ba | ||
|
|
2b3490cce2 | ||
|
|
4baba65cdd | ||
|
|
c6ef96d642 | ||
|
|
385ce9ba37 | ||
|
|
d128490da5 | ||
|
|
60f5b3ffeb | ||
|
|
7d496b03e4 | ||
|
|
261c9aa51d | ||
|
|
5ac5d732a9 | ||
|
|
f2e9bc9f92 | ||
|
|
6d4b8ecfde | ||
|
|
2da366e1cd | ||
|
|
e902405c93 | ||
|
|
ecbf6d0248 | ||
|
|
d0fd66df00 | ||
|
|
7f036c170e | ||
|
|
ce612394dc | ||
|
|
65b2947c18 | ||
|
|
f9147dae85 | ||
|
|
21309953ee | ||
|
|
bc2f37df37 | ||
|
|
57ffac4432 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"jazz-inspector": patch
|
||||
---
|
||||
|
||||
add z-index of 1 to inspector
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"jazz-tools": patch
|
||||
---
|
||||
|
||||
Throw when assigning invalid values to ref fields
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"jazz-tools": patch
|
||||
---
|
||||
|
||||
Use RegisteredAccount types for `by` props
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"jazz-tools": patch
|
||||
---
|
||||
|
||||
Fixes coList.splice to handle insertions at start of list
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.80",
|
||||
"version": "1.0.82",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.65",
|
||||
"version": "0.0.67",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.45",
|
||||
"version": "0.0.47",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "filestream",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.59",
|
||||
"version": "0.0.61",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multiauth",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.55",
|
||||
"version": "0.0.57",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.49",
|
||||
"version": "0.0.51",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.60",
|
||||
"version": "0.0.62",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.57",
|
||||
"version": "0.0.59",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.59",
|
||||
"version": "0.0.61",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.63",
|
||||
"version": "0.0.65",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
15
homepage/homepage/components/TocProvider.tsx
Normal file
15
homepage/homepage/components/TocProvider.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
15
homepage/homepage/components/docs/TocItemsSetter.tsx
Normal file
15
homepage/homepage/components/docs/TocItemsSetter.tsx
Normal 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;
|
||||
}
|
||||
17
homepage/homepage/lib/TocContext.tsx
Normal file
17
homepage/homepage/lib/TocContext.tsx
Normal 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;
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 .",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:*"
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
// Import Twind setup
|
||||
import "./twind.js";
|
||||
|
||||
export { JazzInspector } from "./viewer/new-app.js";
|
||||
|
||||
54
packages/jazz-inspector/src/twind.config.ts
Normal file
54
packages/jazz-inspector/src/twind.config.ts
Normal 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
8
packages/jazz-inspector/src/twind.ts
Normal file
8
packages/jazz-inspector/src/twind.ts
Normal 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 };
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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} />
|
||||
) : (
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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} />;
|
||||
};
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user