Compare commits
197 Commits
jazz-react
...
docs/loadi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e6da19d5e | ||
|
|
10de4b6fc9 | ||
|
|
2433344778 | ||
|
|
4c01459942 | ||
|
|
8dacdd6e2f | ||
|
|
5b0580bfda | ||
|
|
2bed7e845d | ||
|
|
76976026b7 | ||
|
|
10ea8fbf88 | ||
|
|
bd86b159b9 | ||
|
|
c4f5241818 | ||
|
|
181f433477 | ||
|
|
3897a7e137 | ||
|
|
5082ecef3f | ||
|
|
268e433870 | ||
|
|
6c185160c5 | ||
|
|
d18323b74a | ||
|
|
edd91791c9 | ||
|
|
958b13c050 | ||
|
|
bbe140f7be | ||
|
|
1625f82ab7 | ||
|
|
843f729a62 | ||
|
|
cc4631bb42 | ||
|
|
3665ef0088 | ||
|
|
e13039818e | ||
|
|
2e79487982 | ||
|
|
fc2c045a8d | ||
|
|
69bb94be06 | ||
|
|
228c8fa796 | ||
|
|
a34b850824 | ||
|
|
96d518bb97 | ||
|
|
8355f5674d | ||
|
|
5c1d04ee88 | ||
|
|
2ef98d01b0 | ||
|
|
15a86a3014 | ||
|
|
9c49704cf9 | ||
|
|
44f0d8d5c7 | ||
|
|
d7af97d63f | ||
|
|
3b189e4def | ||
|
|
7fe50922cc | ||
|
|
b04e2be665 | ||
|
|
80b1535cdf | ||
|
|
dda9672dcb | ||
|
|
a86cd0e74e | ||
|
|
95e84a2ca7 | ||
|
|
e7c85b7575 | ||
|
|
8ad57f9d50 | ||
|
|
4a0f70692f | ||
|
|
51db96e8d0 | ||
|
|
4b0b27bde7 | ||
|
|
29a177224d | ||
|
|
63ccdaa3b2 | ||
|
|
8ed144e857 | ||
|
|
c9e2ab69bf | ||
|
|
4263c30753 | ||
|
|
416dd79b50 | ||
|
|
11da4d1366 | ||
|
|
6e794c3fed | ||
|
|
080a718c4d | ||
|
|
e5891f77ca | ||
|
|
e141c8779b | ||
|
|
868c49d096 | ||
|
|
bdc78c55fc | ||
|
|
2dbe990c22 | ||
|
|
60b8dbc33c | ||
|
|
5337edf717 | ||
|
|
fe60a88de9 | ||
|
|
939e1d7a3c | ||
|
|
19871690e4 | ||
|
|
064900d5f9 | ||
|
|
8e44caaebc | ||
|
|
c95f344c41 | ||
|
|
7320adc58d | ||
|
|
57a92f4aa0 | ||
|
|
c9b2b01928 | ||
|
|
5b97ac3b92 | ||
|
|
09f0a98eef | ||
|
|
3d8babdbb6 | ||
|
|
f10004c6bc | ||
|
|
33ecca3d10 | ||
|
|
bc601d809b | ||
|
|
cc8462f071 | ||
|
|
790d5dde40 | ||
|
|
7326a19373 | ||
|
|
f39c87b181 | ||
|
|
e19681a4a7 | ||
|
|
c5a3b29c61 | ||
|
|
79513379c8 | ||
|
|
1271a6f753 | ||
|
|
77f58ddcad | ||
|
|
7e945b5eac | ||
|
|
deea2222cb | ||
|
|
ff4a839dca | ||
|
|
fb053a0dd5 | ||
|
|
253b775bff | ||
|
|
831fce6d55 | ||
|
|
e3f85f997c | ||
|
|
44043991fb | ||
|
|
60af229bcc | ||
|
|
be6dafc36a | ||
|
|
cdf3ed898f | ||
|
|
d2379eacd7 | ||
|
|
0525d5c056 | ||
|
|
7803a7a85e | ||
|
|
e9d131cc9c | ||
|
|
4f2730fa00 | ||
|
|
b416136b12 | ||
|
|
56869c3593 | ||
|
|
bce7dade72 | ||
|
|
2c00f93141 | ||
|
|
1bfa9bb1de | ||
|
|
9106881d19 | ||
|
|
75319a6eaf | ||
|
|
01e54ddf4e | ||
|
|
8f6e16a899 | ||
|
|
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 | ||
|
|
3d882f0442 | ||
|
|
f61d568c9d | ||
|
|
02fe68d207 | ||
|
|
0f87cfbbb0 | ||
|
|
3615b4871b | ||
|
|
cc1eb6da90 | ||
|
|
f7b91b7ce1 | ||
|
|
e2b1247969 | ||
|
|
6dd02d289c | ||
|
|
33a4944ba3 | ||
|
|
e367b6056d | ||
|
|
f3f56b9be0 | ||
|
|
4cae6bad34 | ||
|
|
17f2ef57de | ||
|
|
3a4d111a37 | ||
|
|
1e18c7f5fc | ||
|
|
8c7a6b27ed | ||
|
|
91f96e1188 | ||
|
|
65b2947c18 | ||
|
|
f9147dae85 | ||
|
|
28dac10723 | ||
|
|
9cb11e38dd | ||
|
|
21309953ee | ||
|
|
bc2f37df37 | ||
|
|
57ffac4432 | ||
|
|
f3e4bacb33 | ||
|
|
626d43f07b | ||
|
|
1f5d073035 | ||
|
|
a3b607e799 | ||
|
|
8fb93502af | ||
|
|
36774122e0 | ||
|
|
a6923128c1 | ||
|
|
706ca62feb | ||
|
|
01523dcca3 | ||
|
|
77f039b561 | ||
|
|
d661ba77be | ||
|
|
f8fbc59b6f | ||
|
|
cce0d22007 | ||
|
|
e3ff76e9cb | ||
|
|
4cbf71bff7 | ||
|
|
ceb060243a | ||
|
|
a70bebb96a | ||
|
|
b3b2507c35 | ||
|
|
6a8fa16b49 | ||
|
|
1f08807701 | ||
|
|
ba4a7f6170 | ||
|
|
a2854e3602 | ||
|
|
4ea87dc494 | ||
|
|
d8c87c5314 | ||
|
|
46f624a12e | ||
|
|
86ce770f38 |
6
.changeset/breezy-boxes-unite.md
Normal file
6
.changeset/breezy-boxes-unite.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"jazz-tools": minor
|
||||||
|
"cojson": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Check CoValue access permissions when loading
|
||||||
5
.changeset/fast-beans-decide.md
Normal file
5
.changeset/fast-beans-decide.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"jazz-tools": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Implement new API for deep loading
|
||||||
5
.changeset/forty-tips-swim.md
Normal file
5
.changeset/forty-tips-swim.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"jazz-tools": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
The .load function now returns `null` on error
|
||||||
5
.changeset/funny-birds-flash.md
Normal file
5
.changeset/funny-birds-flash.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"cojson": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Return the EVERYONE role if the account is not direct a member of the group
|
||||||
5
.changeset/thin-melons-give.md
Normal file
5
.changeset/thin-melons-give.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"jazz-vue": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix types compilation for useAccount
|
||||||
@@ -1,5 +1,16 @@
|
|||||||
# chat-rn-clerk
|
# chat-rn-clerk
|
||||||
|
|
||||||
|
## 1.0.87
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-react-native@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-react-native-auth-clerk@0.11.6
|
||||||
|
- jazz-react-native-media-images@0.11.6
|
||||||
|
|
||||||
## 1.0.86
|
## 1.0.86
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default function Conversation() {
|
|||||||
const { me } = useAccount();
|
const { me } = useAccount();
|
||||||
const [chat, setChat] = useState<Chat>();
|
const [chat, setChat] = useState<Chat>();
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
const loadedChat = useCoState(Chat, chat?.id, { resolve: { $each: true } });
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export default function Conversation() {
|
|||||||
|
|
||||||
const loadChat = async (chatId: ID<Chat>) => {
|
const loadChat = async (chatId: ID<Chat>) => {
|
||||||
try {
|
try {
|
||||||
const chat = await Chat.load(chatId, me, []);
|
const chat = await Chat.load(chatId, me);
|
||||||
setChat(chat);
|
setChat(chat);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error loading chat", error);
|
console.log("Error loading chat", error);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "chat-rn-clerk",
|
"name": "chat-rn-clerk",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"version": "1.0.86",
|
"version": "1.0.87",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "expo export -p ios",
|
"build": "expo export -p ios",
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export function JazzAndAuth({ children }: PropsWithChildren) {
|
|||||||
storage="sqlite"
|
storage="sqlite"
|
||||||
sync={{
|
sync={{
|
||||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||||
when: "signedUp", // This makes the app work in local mode when the user is not authenticated
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# chat-rn
|
# chat-rn
|
||||||
|
|
||||||
|
## 1.0.83
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-react-native@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
|
||||||
## 1.0.82
|
## 1.0.82
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "chat-rn",
|
"name": "chat-rn",
|
||||||
"version": "1.0.82",
|
"version": "1.0.83",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "expo export -p ios",
|
"build": "expo export -p ios",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { Chat, Message } from "./schema";
|
|||||||
export default function ChatScreen({ navigation }: { navigation: any }) {
|
export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||||
const { me, logOut } = useAccount();
|
const { me, logOut } = useAccount();
|
||||||
const [chatId, setChatId] = useState<ID<Chat>>();
|
const [chatId, setChatId] = useState<ID<Chat>>();
|
||||||
const loadedChat = useCoState(Chat, chatId, [{}]);
|
const loadedChat = useCoState(Chat, chatId, { resolve: { $each: true } });
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const profile = useCoState(Profile, me._refs.profile?.id, {});
|
const profile = useCoState(Profile, me._refs.profile?.id, {});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# chat-vue
|
# chat-vue
|
||||||
|
|
||||||
|
## 0.0.68
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-vue@0.11.6
|
||||||
|
- jazz-browser@0.11.6
|
||||||
|
|
||||||
## 0.0.67
|
## 0.0.67
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example chat-vue --project-name chat-vue
|
npx create-jazz-app@latest chat-vue-app --example chat-vue
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd chat-vue
|
cd chat-vue-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "chat-vue",
|
"name": "chat-vue",
|
||||||
"version": "0.0.67",
|
"version": "0.0.68",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const chat = useCoState(Chat, props.chatId, [{}]);
|
const chat = useCoState(Chat, props.chatId, { resolve: { $each: true } });
|
||||||
const showNLastMessages = ref(30);
|
const showNLastMessages = ref(30);
|
||||||
|
|
||||||
const displayedMessages = computed(() => {
|
const displayedMessages = computed(() => {
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# jazz-example-chat
|
# jazz-example-chat
|
||||||
|
|
||||||
|
## 0.0.165
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- Updated dependencies [09f0a98]
|
||||||
|
- Updated dependencies [11da4d1]
|
||||||
|
- Updated dependencies [8ed144e]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-inspector@0.11.6
|
||||||
|
- jazz-browser-media-images@0.11.6
|
||||||
|
|
||||||
## 0.0.164
|
## 0.0.164
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example chat --project-name chat
|
npx create-jazz-app@latest chat-app --example chat
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd chat
|
cd chat-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-example-chat",
|
"name": "jazz-example-chat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.164",
|
"version": "0.0.165",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"hash-slash": "workspace:*",
|
"hash-slash": "workspace:*",
|
||||||
"jazz-browser-media-images": "workspace:*",
|
"jazz-browser-media-images": "workspace:*",
|
||||||
|
"jazz-inspector": "workspace:*",
|
||||||
"jazz-react": "workspace:*",
|
"jazz-react": "workspace:*",
|
||||||
"jazz-tools": "workspace:*",
|
"jazz-tools": "workspace:*",
|
||||||
"lucide-react": "^0.274.0",
|
"lucide-react": "^0.274.0",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { apiKey } from "@/apiKey.ts";
|
import { apiKey } from "@/apiKey.ts";
|
||||||
import { getRandomUsername, inIframe, onChatLoad } from "@/util.ts";
|
import { getRandomUsername, inIframe, onChatLoad } from "@/util.ts";
|
||||||
import { useIframeHashRouter } from "hash-slash";
|
import { useIframeHashRouter } from "hash-slash";
|
||||||
|
import { JazzInspector } from "jazz-inspector";
|
||||||
import { JazzProvider, useAccount } from "jazz-react";
|
import { JazzProvider, useAccount } from "jazz-react";
|
||||||
import { Group, ID } from "jazz-tools";
|
import { Group, ID } from "jazz-tools";
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
@@ -61,6 +62,7 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
defaultProfileName={defaultProfileName}
|
defaultProfileName={defaultProfileName}
|
||||||
>
|
>
|
||||||
<App />
|
<App />
|
||||||
|
<JazzInspector />
|
||||||
</JazzProvider>
|
</JazzProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
</ThemeProvider>,
|
</ThemeProvider>,
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import {
|
|||||||
} from "./ui.tsx";
|
} from "./ui.tsx";
|
||||||
|
|
||||||
export function ChatScreen(props: { chatID: ID<Chat> }) {
|
export function ChatScreen(props: { chatID: ID<Chat> }) {
|
||||||
|
const chat = useCoState(Chat, props.chatID, { resolve: { $each: true } });
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const chat = useCoState(Chat, props.chatID, [{}]);
|
|
||||||
const [showNLastMessages, setShowNLastMessages] = useState(30);
|
const [showNLastMessages, setShowNLastMessages] = useState(30);
|
||||||
|
|
||||||
if (!chat)
|
if (!chat)
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# minimal-auth-clerk
|
# minimal-auth-clerk
|
||||||
|
|
||||||
|
## 0.0.64
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-react-auth-clerk@0.11.6
|
||||||
|
|
||||||
## 0.0.63
|
## 0.0.63
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example clerk --project-name clerk
|
npx create-jazz-app@latest clerk-app --example clerk
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd clerk
|
cd clerk-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "clerk",
|
"name": "clerk",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.63",
|
"version": "0.0.64",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clerk/clerk-react": "^5.4.1",
|
"@clerk/clerk-react": "^5.4.1",
|
||||||
"jazz-react": "workspace:*",
|
"jazz-react": "workspace:*",
|
||||||
"jazz-react-auth-clerk": "workspace:0.11.5",
|
"jazz-react-auth-clerk": "workspace:0.11.6",
|
||||||
"jazz-tools": "workspace:*",
|
"jazz-tools": "workspace:*",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1"
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ function JazzProvider({ children }: { children: React.ReactNode }) {
|
|||||||
clerk={clerk}
|
clerk={clerk}
|
||||||
sync={{
|
sync={{
|
||||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||||
when: "signedUp", // This makes the app work in local mode when the user is not authenticated
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# file-share-svelte
|
# file-share-svelte
|
||||||
|
|
||||||
|
## 0.0.48
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-svelte@0.11.6
|
||||||
|
|
||||||
## 0.0.47
|
## 0.0.47
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "file-share-svelte",
|
"name": "file-share-svelte",
|
||||||
"version": "0.0.47",
|
"version": "0.0.48",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
AccountSchema={FileShareAccount}
|
AccountSchema={FileShareAccount}
|
||||||
sync={{
|
sync={{
|
||||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||||
when: "signedUp",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PasskeyAuthBasicUI appName="File Share">
|
<PasskeyAuthBasicUI appName="File Share">
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# jazz-tailwind-demo-auth-starter
|
# jazz-tailwind-demo-auth-starter
|
||||||
|
|
||||||
|
## 0.0.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
|
||||||
## 0.0.3
|
## 0.0.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "filestream",
|
"name": "filestream",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# form
|
# form
|
||||||
|
|
||||||
|
## 0.1.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- Updated dependencies [8ed144e]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-browser-media-images@0.11.6
|
||||||
|
|
||||||
## 0.1.5
|
## 0.1.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example form --project-name form
|
npx create-jazz-app@latest form-app --example form
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd form
|
cd form-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "form",
|
"name": "form",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import {
|
|||||||
} from "./schema.ts";
|
} from "./schema.ts";
|
||||||
|
|
||||||
export function CreateOrder() {
|
export function CreateOrder() {
|
||||||
const { me } = useAccount({ root: { draft: {}, orders: [] } });
|
const { me } = useAccount({
|
||||||
|
resolve: { root: { draft: true, orders: true } },
|
||||||
|
});
|
||||||
const router = useIframeHashRouter();
|
const router = useIframeHashRouter();
|
||||||
const [errors, setErrors] = useState<string[]>([]);
|
const [errors, setErrors] = useState<string[]>([]);
|
||||||
|
|
||||||
@@ -60,7 +62,7 @@ function CreateOrderForm({
|
|||||||
onSave: (draft: DraftBubbleTeaOrder) => void;
|
onSave: (draft: DraftBubbleTeaOrder) => void;
|
||||||
}) {
|
}) {
|
||||||
const draft = useCoState(DraftBubbleTeaOrder, id, {
|
const draft = useCoState(DraftBubbleTeaOrder, id, {
|
||||||
addOns: [],
|
resolve: { addOns: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!draft) return;
|
if (!draft) return;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useAccount } from "jazz-react";
|
|||||||
|
|
||||||
export function DraftIndicator() {
|
export function DraftIndicator() {
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: { draft: {} },
|
resolve: { root: { draft: true } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (me?.root.draft?.hasChanges) {
|
if (me?.root.draft?.hasChanges) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { OrderThumbnail } from "./OrderThumbnail.tsx";
|
|||||||
import { BubbleTeaOrder } from "./schema.ts";
|
import { BubbleTeaOrder } from "./schema.ts";
|
||||||
|
|
||||||
export function EditOrder(props: { id: ID<BubbleTeaOrder> }) {
|
export function EditOrder(props: { id: ID<BubbleTeaOrder> }) {
|
||||||
const order = useCoState(BubbleTeaOrder, props.id, []);
|
const order = useCoState(BubbleTeaOrder, props.id);
|
||||||
|
|
||||||
if (!order) return;
|
if (!order) return;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { OrderThumbnail } from "./OrderThumbnail.tsx";
|
|||||||
|
|
||||||
export function Orders() {
|
export function Orders() {
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: { orders: [] },
|
resolve: { root: { orders: true } },
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# image-upload
|
# image-upload
|
||||||
|
|
||||||
|
## 0.0.62
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- Updated dependencies [8ed144e]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-browser-media-images@0.11.6
|
||||||
|
|
||||||
## 0.0.61
|
## 0.0.61
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example image-upload --project-name image-upload
|
npx create-jazz-app@latest image-upload-app --example image-upload
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd image-upload
|
cd image-upload-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "image-upload",
|
"name": "image-upload",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.61",
|
"version": "0.0.62",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
"globals": "^15.11.0",
|
"globals": "^15.11.0",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^6.0.11"
|
"vite": "^6.0.11",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.27",
|
||||||
|
"tailwindcss": "^3.4.17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
examples/image-upload/postcss.config.js
Normal file
6
examples/image-upload/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ import ImageUpload from "./ImageUpload.tsx";
|
|||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="container">
|
<main className="container py-16">
|
||||||
<ImageUpload />
|
<ImageUpload />
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,60 +1,97 @@
|
|||||||
import { createImage } from "jazz-browser-media-images";
|
import { createImage } from "jazz-browser-media-images";
|
||||||
import { ProgressiveImg, useAccount } from "jazz-react";
|
import { ProgressiveImg, useAccount } from "jazz-react";
|
||||||
import { ImageDefinition } from "jazz-tools";
|
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||||
import { ChangeEvent, useRef } from "react";
|
|
||||||
|
|
||||||
function Image({ image }: { image: ImageDefinition }) {
|
|
||||||
return (
|
|
||||||
<ProgressiveImg image={image}>
|
|
||||||
{({ src }) => <img src={src} />}
|
|
||||||
</ProgressiveImg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ImageUpload() {
|
export default function ImageUpload() {
|
||||||
const { me } = useAccount();
|
const { me } = useAccount();
|
||||||
|
const [imagePreviewUrl, setImagePreviewUrl] = useState<string | null>(null);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
|
if (imagePreviewUrl) {
|
||||||
|
e.preventDefault();
|
||||||
|
return "Upload in progress. Are you sure you want to leave?";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
|
||||||
|
if (imagePreviewUrl) {
|
||||||
|
URL.revokeObjectURL(imagePreviewUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [imagePreviewUrl]);
|
||||||
|
|
||||||
const onImageChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
const onImageChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!me?.profile) return;
|
if (!me?.profile) return;
|
||||||
|
|
||||||
const file = event.currentTarget.files?.[0];
|
const file = event.currentTarget.files?.[0];
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
me.profile.image = await createImage(file, {
|
const objectUrl = URL.createObjectURL(file);
|
||||||
owner: me.profile._owner,
|
setImagePreviewUrl(objectUrl);
|
||||||
});
|
|
||||||
|
try {
|
||||||
|
me.profile.image = await createImage(file, {
|
||||||
|
owner: me.profile._owner,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error uploading image:", error);
|
||||||
|
} finally {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
setImagePreviewUrl(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteImage = () => {
|
const deleteImage = () => {
|
||||||
if (!me?.profile) return;
|
if (!me?.profile) return;
|
||||||
|
|
||||||
me.profile.image = null;
|
me.profile.image = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
if (me?.profile?.image) {
|
||||||
<>
|
return (
|
||||||
<div>{me?.profile?.image && <Image image={me.profile.image} />}</div>
|
<>
|
||||||
|
<ProgressiveImg image={me.profile.image}>
|
||||||
|
{({ src }) => <img alt="" src={src} className="w-full h-auto" />}
|
||||||
|
</ProgressiveImg>
|
||||||
|
|
||||||
<div>
|
<button type="button" onClick={deleteImage} className="mt-5">
|
||||||
{me?.profile?.image ? (
|
Delete image
|
||||||
<button type="button" onClick={deleteImage}>
|
</button>
|
||||||
Delete image
|
</>
|
||||||
</button>
|
);
|
||||||
) : (
|
}
|
||||||
<div>
|
|
||||||
<label>Upload image</label>
|
if (imagePreviewUrl) {
|
||||||
<input
|
return (
|
||||||
ref={inputRef}
|
<div className="relative">
|
||||||
type="file"
|
<p className="z-10 absolute font-semibold text-gray-900 inset-0 flex items-center justify-center">
|
||||||
accept="image/png, image/jpeg, image/gif"
|
Uploading image...
|
||||||
onChange={onImageChange}
|
</p>
|
||||||
/>
|
<img
|
||||||
</div>
|
src={imagePreviewUrl}
|
||||||
)}
|
alt="Preview"
|
||||||
|
className="opacity-50 w-full h-auto"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<label htmlFor="image">Image</label>
|
||||||
|
<input
|
||||||
|
id="image"
|
||||||
|
name="image"
|
||||||
|
ref={inputRef}
|
||||||
|
type="file"
|
||||||
|
accept="image/png, image/jpeg, image/gif, image/bmp"
|
||||||
|
onChange={onImageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +1,3 @@
|
|||||||
:root {
|
@tailwind base;
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
@tailwind components;
|
||||||
line-height: 1.5;
|
@tailwind utilities;
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
--border-color: #2f2e2d;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#root {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 0;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-weight: 500;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
--border-color: #e5e5e5;
|
|
||||||
color: #213547;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #747bff;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
border-color: var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
header,
|
|
||||||
main {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
padding: 2rem 0.75rem;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|||||||
18
examples/image-upload/tailwind.config.ts
Normal file
18
examples/image-upload/tailwind.config.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: {
|
||||||
|
DEFAULT: "0.75rem",
|
||||||
|
sm: "1rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -1,5 +1,18 @@
|
|||||||
# jazz-example-inspector
|
# jazz-example-inspector
|
||||||
|
|
||||||
|
## 0.0.115
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 09f0a98: UI and JSON display improvements
|
||||||
|
- 11da4d1: isolate class name hashing on inspector
|
||||||
|
- Updated dependencies [09f0a98]
|
||||||
|
- Updated dependencies [11da4d1]
|
||||||
|
- Updated dependencies [8ed144e]
|
||||||
|
- jazz-inspector@0.11.6
|
||||||
|
- cojson@0.11.6
|
||||||
|
- cojson-transport-ws@0.11.6
|
||||||
|
|
||||||
## 0.0.114
|
## 0.0.114
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-inspector-app",
|
"name": "jazz-inspector-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.114",
|
"version": "0.0.115",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -11,27 +11,19 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"jazz-inspector": "workspace:*",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
|
||||||
"@radix-ui/react-toast": "^1.1.4",
|
|
||||||
"class-variance-authority": "^0.7.0",
|
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"cojson": "workspace:0.11.5",
|
"cojson": "workspace:0.11.6",
|
||||||
"cojson-transport-ws": "workspace:0.11.5",
|
"cojson-transport-ws": "workspace:0.11.6",
|
||||||
"hash-slash": "workspace:0.2.2",
|
"hash-slash": "workspace:0.2.2",
|
||||||
"lucide-react": "^0.274.0",
|
"lucide-react": "^0.274.0",
|
||||||
"qrcode": "^1.5.3",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router": "^6.16.0",
|
"react-router": "^6.16.0",
|
||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.16.0",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0"
|
||||||
"tailwind-merge": "^1.14.0",
|
|
||||||
"tailwindcss-animate": "^1.0.7",
|
|
||||||
"uniqolor": "^1.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/qrcode": "^1.5.1",
|
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||||
|
|||||||
@@ -1,92 +1,3 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
|
||||||
:root {
|
|
||||||
--background: 0 0% 100%;
|
|
||||||
--foreground: 20 14.3% 4.1%;
|
|
||||||
|
|
||||||
--card: 0 0% 100%;
|
|
||||||
--card-foreground: 20 14.3% 4.1%;
|
|
||||||
|
|
||||||
--popover: 0 0% 100%;
|
|
||||||
--popover-foreground: 20 14.3% 4.1%;
|
|
||||||
|
|
||||||
--primary: 24 9.8% 10%;
|
|
||||||
--primary-foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--secondary: 60 4.8% 95.9%;
|
|
||||||
--secondary-foreground: 24 9.8% 10%;
|
|
||||||
|
|
||||||
--muted: 60 4.8% 95.9%;
|
|
||||||
--muted-foreground: 25 5.3% 44.7%;
|
|
||||||
|
|
||||||
--accent: 60 4.8% 95.9%;
|
|
||||||
--accent-foreground: 24 9.8% 10%;
|
|
||||||
|
|
||||||
--destructive: 0 84.2% 60.2%;
|
|
||||||
--destructive-foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--border: 20 5.9% 90%;
|
|
||||||
--input: 20 5.9% 90%;
|
|
||||||
--ring: 20 14.3% 4.1%;
|
|
||||||
|
|
||||||
--radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: 20 14.3% 4.1%;
|
|
||||||
--foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--card: 20 14.3% 4.1%;
|
|
||||||
--card-foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--popover: 20 14.3% 4.1%;
|
|
||||||
--popover-foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--primary: 60 9.1% 97.8%;
|
|
||||||
--primary-foreground: 24 9.8% 10%;
|
|
||||||
|
|
||||||
--secondary: 12 6.5% 15.1%;
|
|
||||||
--secondary-foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--muted: 12 6.5% 15.1%;
|
|
||||||
--muted-foreground: 24 5.4% 63.9%;
|
|
||||||
|
|
||||||
--accent: 12 6.5% 15.1%;
|
|
||||||
--accent-foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
|
||||||
--destructive-foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--border: 12 6.5% 15.1%;
|
|
||||||
--input: 12 6.5% 15.1%;
|
|
||||||
--ring: 24 5.7% 82.9%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-in {
|
|
||||||
animation: slideIn 0.3s ease-out;
|
|
||||||
}
|
|
||||||
@keyframes slideIn {
|
|
||||||
from {
|
|
||||||
transform: translateZ(400px) translateY(30px) scale(1.05);
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateZ(0) scale(1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
export function LinkIcon() {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth={1.5}
|
|
||||||
stroke="currentColor"
|
|
||||||
className="w-3 h-3"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { PageInfo } from "./types";
|
|
||||||
|
|
||||||
interface BreadcrumbsProps {
|
|
||||||
path: PageInfo[];
|
|
||||||
onBreadcrumbClick: (index: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
|
|
||||||
path,
|
|
||||||
onBreadcrumbClick,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="z-20 relative bg-indigo-400/10 backdrop-blur-sm rounded-lg inline-flex px-2 py-1 whitespace-pre transition-all items-center space-x-1 min-h-10">
|
|
||||||
<button
|
|
||||||
onClick={() => onBreadcrumbClick(-1)}
|
|
||||||
className="flex items-center justify-center p-1 rounded-sm hover:bg-indigo-500/10 transition-colors"
|
|
||||||
aria-label="Go to home"
|
|
||||||
>
|
|
||||||
<img src="jazz-logo.png" alt="Jazz Logo" className="size-5" />
|
|
||||||
</button>
|
|
||||||
{path.map((page, index) => {
|
|
||||||
return (
|
|
||||||
<span key={index} className="inline-block first:pl-1 last:pr-1">
|
|
||||||
{index === 0 ? null : (
|
|
||||||
<span className="text-indigo-500/30">{" / "}</span>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => onBreadcrumbClick(index)}
|
|
||||||
className="text-indigo-700 hover:underline"
|
|
||||||
>
|
|
||||||
{index === 0 ? page.name || "Root" : page.name}
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,344 +0,0 @@
|
|||||||
import {
|
|
||||||
CoID,
|
|
||||||
LocalNode,
|
|
||||||
RawBinaryCoStream,
|
|
||||||
RawCoStream,
|
|
||||||
RawCoValue,
|
|
||||||
} from "cojson";
|
|
||||||
import { base64URLtoBytes } from "cojson";
|
|
||||||
import { BinaryStreamItem, BinaryStreamStart, CoStreamItem } from "cojson";
|
|
||||||
import { JsonObject, JsonValue } from "cojson";
|
|
||||||
import { ArrowDownToLine } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { PageInfo } from "./types";
|
|
||||||
import { AccountOrGroupPreview } from "./value-renderer";
|
|
||||||
|
|
||||||
// typeguard for BinaryStreamStart
|
|
||||||
function isBinaryStreamStart(item: unknown): item is BinaryStreamStart {
|
|
||||||
return (
|
|
||||||
typeof item === "object" &&
|
|
||||||
item !== null &&
|
|
||||||
"type" in item &&
|
|
||||||
item.type === "start"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function detectCoStreamType(value: RawCoStream | RawBinaryCoStream) {
|
|
||||||
const firstKey = Object.keys(value.items)[0];
|
|
||||||
if (!firstKey)
|
|
||||||
return {
|
|
||||||
type: "unknown",
|
|
||||||
};
|
|
||||||
|
|
||||||
const items = value.items[firstKey as never]?.map((v) => v.value);
|
|
||||||
|
|
||||||
if (!items)
|
|
||||||
return {
|
|
||||||
type: "unknown",
|
|
||||||
};
|
|
||||||
const firstItem = items[0];
|
|
||||||
if (!firstItem)
|
|
||||||
return {
|
|
||||||
type: "unknown",
|
|
||||||
};
|
|
||||||
// This is a binary stream
|
|
||||||
if (isBinaryStreamStart(firstItem)) {
|
|
||||||
return {
|
|
||||||
type: "binary",
|
|
||||||
items: items as BinaryStreamItem[],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
type: "coStream",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getBlobFromCoStream({
|
|
||||||
items,
|
|
||||||
onlyFirstChunk = false,
|
|
||||||
}: {
|
|
||||||
items: BinaryStreamItem[];
|
|
||||||
onlyFirstChunk?: boolean;
|
|
||||||
}) {
|
|
||||||
if (onlyFirstChunk && items.length > 1) {
|
|
||||||
items = items.slice(0, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunks: Uint8Array[] = [];
|
|
||||||
|
|
||||||
const binary_U_prefixLength = 8;
|
|
||||||
|
|
||||||
let lastProgressUpdate = Date.now();
|
|
||||||
|
|
||||||
for (const item of items.slice(1)) {
|
|
||||||
if (item.type === "end") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type !== "chunk") {
|
|
||||||
console.error("Invalid binary stream chunk", item);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunk = base64URLtoBytes(item.chunk.slice(binary_U_prefixLength));
|
|
||||||
// totalLength += chunk.length;
|
|
||||||
chunks.push(chunk);
|
|
||||||
|
|
||||||
if (Date.now() - lastProgressUpdate > 100) {
|
|
||||||
lastProgressUpdate = Date.now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const defaultMime = "mimeType" in items[0] ? items[0].mimeType : null;
|
|
||||||
|
|
||||||
const blob = new Blob(chunks, defaultMime ? { type: defaultMime } : {});
|
|
||||||
|
|
||||||
const mimeType =
|
|
||||||
defaultMime === "" ? await detectPDFMimeType(blob) : defaultMime;
|
|
||||||
|
|
||||||
return {
|
|
||||||
blob,
|
|
||||||
mimeType: mimeType as string,
|
|
||||||
unfinishedChunks: items.length > 1,
|
|
||||||
totalSize:
|
|
||||||
"totalSizeBytes" in items[0]
|
|
||||||
? (items[0].totalSizeBytes as number)
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const detectPDFMimeType = async (blob: Blob): Promise<string> => {
|
|
||||||
const arrayBuffer = await blob.slice(0, 4).arrayBuffer();
|
|
||||||
const uint8Array = new Uint8Array(arrayBuffer);
|
|
||||||
const header = uint8Array.reduce(
|
|
||||||
(acc, byte) => acc + String.fromCharCode(byte),
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (header === "%PDF") {
|
|
||||||
return "application/pdf";
|
|
||||||
}
|
|
||||||
return "application/octet-stream";
|
|
||||||
};
|
|
||||||
|
|
||||||
const BinaryDownloadButton = ({
|
|
||||||
pdfBlob,
|
|
||||||
fileName = "document",
|
|
||||||
label,
|
|
||||||
mimeType,
|
|
||||||
}: {
|
|
||||||
pdfBlob: Blob;
|
|
||||||
mimeType?: string;
|
|
||||||
fileName?: string;
|
|
||||||
label: string;
|
|
||||||
}) => {
|
|
||||||
const downloadFile = () => {
|
|
||||||
const url = URL.createObjectURL(
|
|
||||||
new Blob([pdfBlob], mimeType ? { type: mimeType } : {}),
|
|
||||||
);
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.href = url;
|
|
||||||
link.download =
|
|
||||||
mimeType === "application/pdf" ? `${fileName}.pdf` : fileName;
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="flex items-center gap-2 px-2 py-1 text-gray-900 border border-gray-900/10 bg-clip-border shadow-sm transition-colors rounded bg-gray-50 text-sm"
|
|
||||||
onClick={downloadFile}
|
|
||||||
>
|
|
||||||
<ArrowDownToLine size={16} />
|
|
||||||
{label}
|
|
||||||
{/* Download {mimeType === "application/pdf" ? "PDF" : "File"} */}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const LabelContentPair = ({
|
|
||||||
label,
|
|
||||||
content,
|
|
||||||
}: {
|
|
||||||
label: string;
|
|
||||||
content: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-1.5 ">
|
|
||||||
<span className="uppercase text-xs font-medium text-gray-600 tracking-wide">
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
<span>{content}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function RenderCoBinaryStream({
|
|
||||||
value,
|
|
||||||
items,
|
|
||||||
}: {
|
|
||||||
items: BinaryStreamItem[];
|
|
||||||
value: RawBinaryCoStream;
|
|
||||||
}) {
|
|
||||||
const [file, setFile] = useState<
|
|
||||||
| {
|
|
||||||
blob: Blob;
|
|
||||||
mimeType: string;
|
|
||||||
unfinishedChunks: boolean;
|
|
||||||
totalSize: number | undefined;
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
| null
|
|
||||||
>(null);
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// load only the first chunk to get the mime type and size
|
|
||||||
getBlobFromCoStream({
|
|
||||||
items,
|
|
||||||
onlyFirstChunk: true,
|
|
||||||
})
|
|
||||||
.then((v) => {
|
|
||||||
if (v) {
|
|
||||||
setFile(v);
|
|
||||||
if (v.mimeType.includes("image")) {
|
|
||||||
// If it's an image, load the full blob
|
|
||||||
getBlobFromCoStream({
|
|
||||||
items,
|
|
||||||
}).then((s) => {
|
|
||||||
if (s) setFile(s);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => setIsLoading(false));
|
|
||||||
}, [items]);
|
|
||||||
|
|
||||||
if (!isLoading && !file) return <div>No blob</div>;
|
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>;
|
|
||||||
if (!file) return <div>No blob</div>;
|
|
||||||
|
|
||||||
const { blob, mimeType } = file;
|
|
||||||
|
|
||||||
const sizeInKB = (file.totalSize || 0) / 1024;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-8 mt-4">
|
|
||||||
<div className="grid grid-cols-3 gap-2 max-w-3xl">
|
|
||||||
<LabelContentPair
|
|
||||||
label="Mime Type"
|
|
||||||
content={
|
|
||||||
<span className="font-mono bg-gray-100 rounded px-2 py-1 text-sm">
|
|
||||||
{mimeType || "No mime type"}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<LabelContentPair
|
|
||||||
label="Size"
|
|
||||||
content={<span>{sizeInKB.toFixed(2)} KB</span>}
|
|
||||||
/>
|
|
||||||
<LabelContentPair
|
|
||||||
label="Download"
|
|
||||||
content={
|
|
||||||
<BinaryDownloadButton
|
|
||||||
fileName={value.id.toString()}
|
|
||||||
pdfBlob={blob}
|
|
||||||
mimeType={mimeType}
|
|
||||||
label={
|
|
||||||
mimeType === "application/pdf"
|
|
||||||
? "Download PDF"
|
|
||||||
: "Download File"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{mimeType === "image/png" || mimeType === "image/jpeg" ? (
|
|
||||||
<LabelContentPair
|
|
||||||
label="Preview"
|
|
||||||
content={
|
|
||||||
<div className="bg-gray-50 p-3 rounded-sm">
|
|
||||||
<RenderBlobImage blob={blob} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RenderCoStream({
|
|
||||||
value,
|
|
||||||
node,
|
|
||||||
}: {
|
|
||||||
value: RawCoStream;
|
|
||||||
node: LocalNode;
|
|
||||||
}) {
|
|
||||||
const streamPerUser = Object.keys(value.items);
|
|
||||||
const userCoIds = streamPerUser.map((stream) => stream.split("_session")[0]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
{userCoIds.map((id, idx) => (
|
|
||||||
<div
|
|
||||||
className="bg-gray-100 p-3 rounded-lg transition-colors overflow-hidden bg-white border hover:bg-gray-100/5 cursor-pointer shadow-sm"
|
|
||||||
key={id}
|
|
||||||
>
|
|
||||||
<AccountOrGroupPreview coId={id as CoID<RawCoValue>} node={node} />
|
|
||||||
{/* @ts-expect-error - TODO: fix types */}
|
|
||||||
{value.items[streamPerUser[idx]]?.map(
|
|
||||||
(item: CoStreamItem<JsonValue>) => (
|
|
||||||
<div>
|
|
||||||
{new Date(item.madeAt).toLocaleString()}{" "}
|
|
||||||
{JSON.stringify(item.value)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CoStreamView({
|
|
||||||
value,
|
|
||||||
node,
|
|
||||||
}: {
|
|
||||||
data: JsonObject;
|
|
||||||
onNavigate: (pages: PageInfo[]) => void;
|
|
||||||
node: LocalNode;
|
|
||||||
value: RawCoStream;
|
|
||||||
}) {
|
|
||||||
// if (!value) return <div>No value</div>;
|
|
||||||
|
|
||||||
const streamType = detectCoStreamType(value);
|
|
||||||
|
|
||||||
if (streamType.type === "binary") {
|
|
||||||
if (streamType.items === undefined) {
|
|
||||||
return <div>No binary stream</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RenderCoBinaryStream
|
|
||||||
value={value as RawBinaryCoStream}
|
|
||||||
items={streamType.items}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamType.type === "coStream") {
|
|
||||||
return <RenderCoStream value={value} node={node} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamType.type === "unknown") return <div>Unknown stream type</div>;
|
|
||||||
|
|
||||||
return <div>Unknown stream type</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function RenderBlobImage({ blob }: { blob: Blob }) {
|
|
||||||
const urlCreator = window.URL || window.webkitURL;
|
|
||||||
return <img src={urlCreator.createObjectURL(blob)} />;
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import clsx from "clsx";
|
|
||||||
import { CoID, LocalNode, RawCoValue } from "cojson";
|
|
||||||
import { JsonObject } from "cojson";
|
|
||||||
import { ResolveIcon } from "./type-icon";
|
|
||||||
import { PageInfo, isCoId } from "./types";
|
|
||||||
import { CoMapPreview, ValueRenderer } from "./value-renderer";
|
|
||||||
|
|
||||||
export function GridView({
|
|
||||||
data,
|
|
||||||
onNavigate,
|
|
||||||
node,
|
|
||||||
}: {
|
|
||||||
data: JsonObject;
|
|
||||||
onNavigate: (pages: PageInfo[]) => void;
|
|
||||||
node: LocalNode;
|
|
||||||
}) {
|
|
||||||
const entries = Object.entries(data);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-2">
|
|
||||||
{entries.map(([key, child], childIndex) => (
|
|
||||||
<div
|
|
||||||
key={childIndex}
|
|
||||||
className={clsx(
|
|
||||||
"bg-gray-100 p-3 rounded-lg transition-colors overflow-hidden",
|
|
||||||
isCoId(child)
|
|
||||||
? "bg-white border hover:bg-gray-100/5 cursor-pointer shadow-sm"
|
|
||||||
: "bg-gray-50",
|
|
||||||
)}
|
|
||||||
onClick={() =>
|
|
||||||
isCoId(child) &&
|
|
||||||
onNavigate([{ coId: child as CoID<RawCoValue>, name: key }])
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<h3 className="truncate">
|
|
||||||
{isCoId(child) ? (
|
|
||||||
<span className="font-medium flex justify-between">
|
|
||||||
{key}
|
|
||||||
|
|
||||||
<div className="px-2 py-1 text-xs bg-gray-100 rounded">
|
|
||||||
<ResolveIcon coId={child as CoID<RawCoValue>} node={node} />
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>{key}</span>
|
|
||||||
)}
|
|
||||||
</h3>
|
|
||||||
<div className="mt-2 text-sm">
|
|
||||||
{isCoId(child) ? (
|
|
||||||
<CoMapPreview coId={child as CoID<RawCoValue>} node={node} />
|
|
||||||
) : (
|
|
||||||
<ValueRenderer
|
|
||||||
json={child}
|
|
||||||
onCoIDClick={(coId) => {
|
|
||||||
onNavigate([{ coId, name: key }]);
|
|
||||||
}}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { LocalNode } from "cojson";
|
import { LocalNode } from "cojson";
|
||||||
import { Breadcrumbs } from "./breadcrumbs";
|
import { Breadcrumbs, PageStack } from "jazz-inspector";
|
||||||
import { PageStack } from "./page-stack";
|
import type { PageInfo } from "jazz-inspector";
|
||||||
import { PageInfo } from "./types";
|
|
||||||
import { usePagePath } from "./use-page-path";
|
import { usePagePath } from "./use-page-path";
|
||||||
|
|
||||||
export default function CoJsonViewer({
|
export default function CoJsonViewer({
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ import {
|
|||||||
} from "cojson";
|
} from "cojson";
|
||||||
import { createWebSocketPeer } from "cojson-transport-ws";
|
import { createWebSocketPeer } from "cojson-transport-ws";
|
||||||
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
||||||
import { Trash2 } from "lucide-react";
|
import {
|
||||||
|
Breadcrumbs,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
PageStack,
|
||||||
|
Select,
|
||||||
|
} from "jazz-inspector";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Breadcrumbs } from "./breadcrumbs";
|
|
||||||
import { PageStack } from "./page-stack";
|
|
||||||
import { usePagePath } from "./use-page-path";
|
import { usePagePath } from "./use-page-path";
|
||||||
import { resolveCoValue, useResolvedCoValue } from "./use-resolve-covalue";
|
import { resolveCoValue, useResolvedCoValue } from "./use-resolve-covalue";
|
||||||
|
|
||||||
@@ -121,15 +126,23 @@ export default function CoJsonViewerApp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-screen bg-gray-100 p-4 overflow-hidden flex flex-col">
|
<div
|
||||||
<div className="flex items-center mb-4 gap-4">
|
className={clsx(
|
||||||
|
"h-screen overflow-hidden flex flex-col",
|
||||||
|
" text-stone-700 bg-white",
|
||||||
|
"dark:text-stone-300 dark:bg-stone-950",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<header className="flex items-center gap-4 p-3">
|
||||||
<Breadcrumbs path={path} onBreadcrumbClick={goToIndex} />
|
<Breadcrumbs path={path} onBreadcrumbClick={goToIndex} />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<form onSubmit={handleCoValueIdSubmit}>
|
<form onSubmit={handleCoValueIdSubmit}>
|
||||||
{path.length !== 0 && (
|
{path.length !== 0 && (
|
||||||
<input
|
<Input
|
||||||
className="border p-2 rounded-lg min-w-[21rem] font-mono"
|
className="min-w-[21rem] font-mono"
|
||||||
placeholder="co_z1234567890abcdef123456789"
|
placeholder="co_z1234567890abcdef123456789"
|
||||||
|
label="CoValue ID"
|
||||||
|
hideLabel
|
||||||
value={coValueId}
|
value={coValueId}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setCoValueId(e.target.value as CoID<RawCoValue>)
|
setCoValueId(e.target.value as CoID<RawCoValue>)
|
||||||
@@ -145,7 +158,7 @@ export default function CoJsonViewerApp() {
|
|||||||
deleteCurrentAccount={deleteCurrentAccount}
|
deleteCurrentAccount={deleteCurrentAccount}
|
||||||
localNode={localNode}
|
localNode={localNode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
<PageStack
|
<PageStack
|
||||||
path={path}
|
path={path}
|
||||||
@@ -153,49 +166,39 @@ export default function CoJsonViewerApp() {
|
|||||||
goBack={goBack}
|
goBack={goBack}
|
||||||
addPages={addPages}
|
addPages={addPages}
|
||||||
>
|
>
|
||||||
{!currentAccount ? (
|
{!currentAccount && <AddAccountForm addAccount={addAccount} />}
|
||||||
<AddAccountForm addAccount={addAccount} />
|
|
||||||
) : (
|
{currentAccount && path.length <= 0 && (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleCoValueIdSubmit}
|
onSubmit={handleCoValueIdSubmit}
|
||||||
aria-hidden={path.length !== 0}
|
aria-hidden={path.length !== 0}
|
||||||
className={clsx(
|
className="flex flex-col relative -top-6 justify-center gap-2 h-full w-full max-w-sm mx-auto"
|
||||||
"flex flex-col justify-center items-center gap-2 h-full w-full mb-20 ",
|
|
||||||
"transition-all duration-150",
|
|
||||||
path.length > 0
|
|
||||||
? "opacity-0 -translate-y-2 scale-95"
|
|
||||||
: "opacity-100",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<fieldset className="flex flex-col gap-2 text-sm">
|
<h1 className="text-lg text-center font-medium mb-4 text-stone-900 dark:text-white">
|
||||||
<h2 className="text-3xl font-medium text-gray-950 text-center mb-4">
|
Jazz CoValue Inspector
|
||||||
Jazz CoValue Inspector
|
</h1>
|
||||||
</h2>
|
<Input
|
||||||
<input
|
label="CoValue ID"
|
||||||
className="border p-4 rounded-lg min-w-[21rem] font-mono"
|
className="font-mono"
|
||||||
placeholder="co_z1234567890abcdef123456789"
|
hideLabel
|
||||||
value={coValueId}
|
placeholder="co_z1234567890abcdef123456789"
|
||||||
onChange={(e) =>
|
value={coValueId}
|
||||||
setCoValueId(e.target.value as CoID<RawCoValue>)
|
onChange={(e) => setCoValueId(e.target.value as CoID<RawCoValue>)}
|
||||||
}
|
/>
|
||||||
/>
|
<Button type="submit" variant="primary">
|
||||||
<button
|
Inspect CoValue
|
||||||
type="submit"
|
</Button>
|
||||||
className="bg-indigo-500 hover:bg-indigo-500/80 text-white px-4 py-2 rounded-md"
|
|
||||||
>
|
<p className="text-center">or</p>
|
||||||
Inspect
|
|
||||||
</button>
|
<Button
|
||||||
<hr />
|
variant="secondary"
|
||||||
<button
|
onClick={() => {
|
||||||
type="button"
|
setPage(currentAccount.id);
|
||||||
className="border inline-block px-2 py-1.5 text-black rounded"
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
setPage(currentAccount.id);
|
Inspect my account
|
||||||
}}
|
</Button>
|
||||||
>
|
|
||||||
Inspect My Account
|
|
||||||
</button>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
</PageStack>
|
</PageStack>
|
||||||
@@ -217,8 +220,11 @@ function AccountSwitcher({
|
|||||||
localNode: LocalNode | null;
|
localNode: LocalNode | null;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center gap-1">
|
<div className="relative flex items-stretch gap-1">
|
||||||
<select
|
<Select
|
||||||
|
label="Account to inspect"
|
||||||
|
hideLabel
|
||||||
|
className="label:sr-only max-w-96"
|
||||||
value={currentAccount?.id || "add-account"}
|
value={currentAccount?.id || "add-account"}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "add-account") {
|
if (e.target.value === "add-account") {
|
||||||
@@ -228,7 +234,6 @@ function AccountSwitcher({
|
|||||||
setCurrentAccount(account || null);
|
setCurrentAccount(account || null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="p-2 px-4 bg-gray-100/50 border border-indigo-500/10 backdrop-blur-sm rounded-md text-indigo-700 appearance-none"
|
|
||||||
>
|
>
|
||||||
{accounts.map((account) => (
|
{accounts.map((account) => (
|
||||||
<option key={account.id} value={account.id}>
|
<option key={account.id} value={account.id}>
|
||||||
@@ -240,15 +245,16 @@ function AccountSwitcher({
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
<option value="add-account">Add account</option>
|
<option value="add-account">Add account</option>
|
||||||
</select>
|
</Select>
|
||||||
{currentAccount && (
|
{currentAccount && (
|
||||||
<button
|
<Button
|
||||||
|
variant="secondary"
|
||||||
onClick={deleteCurrentAccount}
|
onClick={deleteCurrentAccount}
|
||||||
className="p-3 rounded hover:bg-gray-200 transition-colors"
|
className="rounded-md p-2 ml-1"
|
||||||
title="Delete Account"
|
aria-label="Remove account"
|
||||||
>
|
>
|
||||||
<Trash2 size={16} className="text-gray-500" />
|
<Icon name="delete" className="text-gray-500" />
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -272,30 +278,34 @@ function AddAccountForm({
|
|||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex flex-col gap-2 max-w-md mx-auto h-full justify-center"
|
className="flex flex-col gap-3 max-w-md mx-auto h-full justify-center"
|
||||||
>
|
>
|
||||||
<h2 className="text-2xl font-medium text-gray-900 mb-3">
|
<h2 className="text-2xl font-medium text-gray-900 dark:text-white">
|
||||||
Add an Account to Inspect
|
Add an account to inspect
|
||||||
</h2>
|
</h2>
|
||||||
<input
|
<p className="leading-relaxed mb-5">
|
||||||
className="border py-2 px-3 rounded-md"
|
Use the{" "}
|
||||||
placeholder="Account ID"
|
<code className="whitespace-nowrap text-stone-900 dark:text-white font-semibold">
|
||||||
|
jazz-logged-in-secret
|
||||||
|
</code>{" "}
|
||||||
|
local storage key from within your Jazz app for your account
|
||||||
|
credentials.
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
label="Account ID"
|
||||||
value={id}
|
value={id}
|
||||||
|
placeholder="co_z1234567890abcdef123456789"
|
||||||
onChange={(e) => setId(e.target.value)}
|
onChange={(e) => setId(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
|
label="Account secret"
|
||||||
type="password"
|
type="password"
|
||||||
className="border py-2 px-3 rounded-md"
|
|
||||||
placeholder="Account Secret"
|
|
||||||
value={secret}
|
value={secret}
|
||||||
onChange={(e) => setSecret(e.target.value)}
|
onChange={(e) => setSecret(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<button
|
<Button className="mt-3" type="submit">
|
||||||
type="submit"
|
Add account
|
||||||
className="bg-indigo-500 text-white px-4 py-2 rounded-md"
|
</Button>
|
||||||
>
|
|
||||||
Add Account
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import { CoID, LocalNode, RawCoValue } from "cojson";
|
|
||||||
import { Page } from "./page"; // Assuming you have a Page component
|
|
||||||
|
|
||||||
// Define the structure of a page in the path
|
|
||||||
interface PageInfo {
|
|
||||||
coId: CoID<RawCoValue>;
|
|
||||||
name?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Props for the PageStack component
|
|
||||||
interface PageStackProps {
|
|
||||||
path: PageInfo[];
|
|
||||||
node?: LocalNode | null;
|
|
||||||
goBack: () => void;
|
|
||||||
addPages: (pages: PageInfo[]) => void;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PageStack({
|
|
||||||
path,
|
|
||||||
node,
|
|
||||||
goBack,
|
|
||||||
addPages,
|
|
||||||
children,
|
|
||||||
}: PageStackProps) {
|
|
||||||
return (
|
|
||||||
<div className="relative mt-4 h-[calc(100vh-6rem)]">
|
|
||||||
{children && <div className="absolute inset-0 pb-20">{children}</div>}
|
|
||||||
{node &&
|
|
||||||
path.map((page, index) => (
|
|
||||||
<Page
|
|
||||||
key={`${page.coId}-${index}`}
|
|
||||||
coId={page.coId}
|
|
||||||
node={node}
|
|
||||||
name={page.name || page.coId}
|
|
||||||
onHeaderClick={goBack}
|
|
||||||
onNavigate={addPages}
|
|
||||||
isTopLevel={index === path.length - 1}
|
|
||||||
style={{
|
|
||||||
transform: `translateZ(${(index - path.length + 1) * 200}px) scale(${
|
|
||||||
1 - (path.length - index - 1) * 0.05
|
|
||||||
}) translateY(${-(index - path.length + 1) * -4}%)`,
|
|
||||||
opacity: 1 - (path.length - index - 1) * 0.05,
|
|
||||||
zIndex: index,
|
|
||||||
transitionProperty: "transform, opacity",
|
|
||||||
transitionDuration: "0.3s",
|
|
||||||
transitionTimingFunction: "ease-out",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import clsx from "clsx";
|
|
||||||
import { CoID, LocalNode, RawCoStream, RawCoValue } from "cojson";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { CoStreamView } from "./co-stream-view";
|
|
||||||
import { GridView } from "./grid-view";
|
|
||||||
import { TableView } from "./table-viewer";
|
|
||||||
import { TypeIcon } from "./type-icon";
|
|
||||||
import { PageInfo } from "./types";
|
|
||||||
import { useResolvedCoValue } from "./use-resolve-covalue";
|
|
||||||
import { AccountOrGroupPreview } from "./value-renderer";
|
|
||||||
|
|
||||||
type PageProps = {
|
|
||||||
coId: CoID<RawCoValue>;
|
|
||||||
node: LocalNode;
|
|
||||||
name: string;
|
|
||||||
onNavigate: (newPages: PageInfo[]) => void;
|
|
||||||
onHeaderClick?: () => void;
|
|
||||||
isTopLevel?: boolean;
|
|
||||||
style: React.CSSProperties;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Page({
|
|
||||||
coId,
|
|
||||||
node,
|
|
||||||
name,
|
|
||||||
onNavigate,
|
|
||||||
onHeaderClick,
|
|
||||||
style,
|
|
||||||
isTopLevel,
|
|
||||||
}: PageProps) {
|
|
||||||
const { value, snapshot, type, extendedType } = useResolvedCoValue(
|
|
||||||
coId,
|
|
||||||
node,
|
|
||||||
);
|
|
||||||
const [viewMode, setViewMode] = useState<"grid" | "table">("grid");
|
|
||||||
|
|
||||||
const supportsTableView = type === "colist" || extendedType === "record";
|
|
||||||
|
|
||||||
// Automatically switch to table view if the page is a CoMap record
|
|
||||||
useEffect(() => {
|
|
||||||
if (supportsTableView) {
|
|
||||||
setViewMode("table");
|
|
||||||
}
|
|
||||||
}, [supportsTableView]);
|
|
||||||
|
|
||||||
if (snapshot === "unavailable") {
|
|
||||||
return <div style={style}>Data unavailable</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!snapshot) {
|
|
||||||
return <div style={style}></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={style}
|
|
||||||
className={clsx(
|
|
||||||
"absolute inset-0 border border-gray-900/5 bg-clip-padding bg-white rounded-xl shadow-lg p-6 animate-in",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{!isTopLevel && (
|
|
||||||
<div
|
|
||||||
className="absolute inset-x-0 top-0 h-10"
|
|
||||||
aria-label="Back"
|
|
||||||
onClick={() => {
|
|
||||||
onHeaderClick?.();
|
|
||||||
}}
|
|
||||||
aria-hidden="true"
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h2 className="text-2xl font-bold flex items-start flex-col gap-1">
|
|
||||||
<span>
|
|
||||||
{name}
|
|
||||||
{typeof snapshot === "object" && "name" in snapshot ? (
|
|
||||||
<span className="text-gray-600 font-medium">
|
|
||||||
{" "}
|
|
||||||
{
|
|
||||||
(
|
|
||||||
snapshot as {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
).name
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
|
|
||||||
{type && <TypeIcon type={type} extendedType={extendedType} />}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
|
|
||||||
{coId}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* {supportsTableView && (
|
|
||||||
<button
|
|
||||||
onClick={toggleViewMode}
|
|
||||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
|
|
||||||
>
|
|
||||||
{viewMode === "grid" ? "Table View" : "Grid View"}
|
|
||||||
</button>
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
<div className="overflow-auto max-h-[calc(100%-4rem)]">
|
|
||||||
{type === "costream" ? (
|
|
||||||
<CoStreamView
|
|
||||||
data={snapshot}
|
|
||||||
onNavigate={onNavigate}
|
|
||||||
node={node}
|
|
||||||
value={value as RawCoStream}
|
|
||||||
/>
|
|
||||||
) : viewMode === "grid" ? (
|
|
||||||
<GridView data={snapshot} onNavigate={onNavigate} node={node} />
|
|
||||||
) : (
|
|
||||||
<TableView data={snapshot} node={node} onNavigate={onNavigate} />
|
|
||||||
)}
|
|
||||||
{/* --- */}
|
|
||||||
{extendedType !== "account" && extendedType !== "group" && (
|
|
||||||
<div className="text-xs text-gray-500 mt-4">
|
|
||||||
Owned by{" "}
|
|
||||||
<AccountOrGroupPreview
|
|
||||||
coId={value.group.id}
|
|
||||||
node={node}
|
|
||||||
showId
|
|
||||||
onClick={() => {
|
|
||||||
onNavigate([{ coId: value.group.id, name: "owner" }]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import { CoID, LocalNode, RawCoValue } from "cojson";
|
|
||||||
import { JsonObject } from "cojson";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import { LinkIcon } from "../link-icon";
|
|
||||||
import { PageInfo } from "./types";
|
|
||||||
import { useResolvedCoValues } from "./use-resolve-covalue";
|
|
||||||
import { ValueRenderer } from "./value-renderer";
|
|
||||||
|
|
||||||
export function TableView({
|
|
||||||
data,
|
|
||||||
node,
|
|
||||||
onNavigate,
|
|
||||||
}: {
|
|
||||||
data: JsonObject;
|
|
||||||
node: LocalNode;
|
|
||||||
onNavigate: (pages: PageInfo[]) => void;
|
|
||||||
}) {
|
|
||||||
const [visibleRowsCount, setVisibleRowsCount] = useState(10);
|
|
||||||
const [coIdArray, visibleRows] = useMemo(() => {
|
|
||||||
const coIdArray = Array.isArray(data)
|
|
||||||
? data
|
|
||||||
: Object.values(data).every(
|
|
||||||
(k) => typeof k === "string" && k.startsWith("co_"),
|
|
||||||
)
|
|
||||||
? Object.values(data).map((k) => k as CoID<RawCoValue>)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const visibleRows = coIdArray.slice(0, visibleRowsCount);
|
|
||||||
|
|
||||||
return [coIdArray, visibleRows];
|
|
||||||
}, [data, visibleRowsCount]);
|
|
||||||
const resolvedRows = useResolvedCoValues(visibleRows, node);
|
|
||||||
|
|
||||||
const hasMore = visibleRowsCount < coIdArray.length;
|
|
||||||
|
|
||||||
if (!coIdArray.length) {
|
|
||||||
return <div>No data to display</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolvedRows.length === 0) {
|
|
||||||
return <div>Loading...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = Array.from(
|
|
||||||
new Set(resolvedRows.flatMap((item) => Object.keys(item.snapshot || {}))),
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadMore = () => {
|
|
||||||
setVisibleRowsCount((prevVisibleRows) => prevVisibleRows + 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
|
||||||
<thead className="sticky top-0 border-b">
|
|
||||||
<tr>
|
|
||||||
{["", ...keys].map((key) => (
|
|
||||||
<th
|
|
||||||
key={key}
|
|
||||||
className="px-4 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 rounded"
|
|
||||||
>
|
|
||||||
{key}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
|
||||||
{resolvedRows.slice(0, visibleRowsCount).map((item, index) => (
|
|
||||||
<tr key={index}>
|
|
||||||
<td className="px-1 py-0">
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
onNavigate([
|
|
||||||
{
|
|
||||||
coId: item.value!.id,
|
|
||||||
name: index.toString(),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}
|
|
||||||
className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 hover:text-blue-500 hover:bg-gray-100 rounded"
|
|
||||||
>
|
|
||||||
<LinkIcon />
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
{keys.map((key) => (
|
|
||||||
<td
|
|
||||||
key={key}
|
|
||||||
className="px-4 py-4 whitespace-nowrap text-sm text-gray-500"
|
|
||||||
>
|
|
||||||
<ValueRenderer
|
|
||||||
json={(item.snapshot as JsonObject)[key]}
|
|
||||||
onCoIDClick={(coId) => {
|
|
||||||
async function handleClick() {
|
|
||||||
onNavigate([
|
|
||||||
{
|
|
||||||
coId: item.value!.id,
|
|
||||||
name: index.toString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
coId: coId,
|
|
||||||
name: key,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div className="py-4 text-gray-500 flex items-center justify-between gap-2">
|
|
||||||
<span>
|
|
||||||
Showing {Math.min(visibleRowsCount, coIdArray.length)} of{" "}
|
|
||||||
{coIdArray.length}
|
|
||||||
</span>
|
|
||||||
{hasMore && (
|
|
||||||
<div className="text-center">
|
|
||||||
<button
|
|
||||||
onClick={loadMore}
|
|
||||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
Load More
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { CoID, LocalNode, RawCoValue } from "cojson";
|
|
||||||
import {
|
|
||||||
CoJsonType,
|
|
||||||
ExtendedCoJsonType,
|
|
||||||
useResolvedCoValue,
|
|
||||||
} from "./use-resolve-covalue";
|
|
||||||
|
|
||||||
export const TypeIcon = ({
|
|
||||||
type,
|
|
||||||
extendedType,
|
|
||||||
}: {
|
|
||||||
type: CoJsonType;
|
|
||||||
extendedType?: ExtendedCoJsonType;
|
|
||||||
}) => {
|
|
||||||
const iconMap: Record<ExtendedCoJsonType | CoJsonType, string> = {
|
|
||||||
record: "{} Record",
|
|
||||||
image: "🖼️ Image",
|
|
||||||
comap: "{} CoMap",
|
|
||||||
costream: "≋ CoStream",
|
|
||||||
colist: "☰ CoList",
|
|
||||||
account: "👤 Account",
|
|
||||||
group: "👥 Group",
|
|
||||||
};
|
|
||||||
|
|
||||||
const iconKey = extendedType || type;
|
|
||||||
const icon = iconMap[iconKey as keyof typeof iconMap];
|
|
||||||
|
|
||||||
return icon ? <span className="font-mono">{icon}</span> : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ResolveIcon = ({
|
|
||||||
coId,
|
|
||||||
node,
|
|
||||||
}: {
|
|
||||||
coId: CoID<RawCoValue>;
|
|
||||||
node: LocalNode;
|
|
||||||
}) => {
|
|
||||||
const { type, extendedType, snapshot } = useResolvedCoValue(coId, node);
|
|
||||||
|
|
||||||
if (snapshot === "unavailable" && !type) {
|
|
||||||
return <div className="text-gray-600 font-medium">Unavailable</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!type) return <div className="whitespace-pre w-14 font-mono"> </div>;
|
|
||||||
|
|
||||||
return <TypeIcon type={type} extendedType={extendedType} />;
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { CoID, RawCoValue } from "cojson";
|
|
||||||
|
|
||||||
export type PageInfo = {
|
|
||||||
coId: CoID<RawCoValue>;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isCoId = (coId: unknown): coId is CoID<RawCoValue> =>
|
|
||||||
typeof coId === "string" && coId.startsWith("co_");
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CoID, RawCoValue } from "cojson";
|
import { CoID, RawCoValue } from "cojson";
|
||||||
|
import { PageInfo } from "jazz-inspector";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { PageInfo } from "./types";
|
|
||||||
|
|
||||||
export function usePagePath(defaultPath?: PageInfo[]) {
|
export function usePagePath(defaultPath?: PageInfo[]) {
|
||||||
const [path, setPath] = useState<PageInfo[]>(() => {
|
const [path, setPath] = useState<PageInfo[]>(() => {
|
||||||
|
|||||||
@@ -1,260 +0,0 @@
|
|||||||
import clsx from "clsx";
|
|
||||||
import { CoID, JsonValue, LocalNode, RawCoValue } from "cojson";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { LinkIcon } from "../link-icon";
|
|
||||||
import {
|
|
||||||
isBrowserImage,
|
|
||||||
resolveCoValue,
|
|
||||||
useResolvedCoValue,
|
|
||||||
} from "./use-resolve-covalue";
|
|
||||||
|
|
||||||
// Is there a chance we can pass the actual CoValue here?
|
|
||||||
export function ValueRenderer({
|
|
||||||
json,
|
|
||||||
compact,
|
|
||||||
onCoIDClick,
|
|
||||||
}: {
|
|
||||||
json: JsonValue | undefined;
|
|
||||||
compact?: boolean;
|
|
||||||
onCoIDClick?: (childNode: CoID<RawCoValue>) => void;
|
|
||||||
}) {
|
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
|
||||||
|
|
||||||
if (typeof json === "undefined" || json === undefined) {
|
|
||||||
return <span className="text-gray-400">undefined</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json === null) {
|
|
||||||
return <span className="text-gray-400">null</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof json === "string" && json.startsWith("co_")) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
"inline-flex gap-1 items-center",
|
|
||||||
onCoIDClick && "text-blue-500 cursor-pointer hover:underline",
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
onCoIDClick?.(json as CoID<RawCoValue>);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{json}
|
|
||||||
{onCoIDClick && <LinkIcon />}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof json === "string") {
|
|
||||||
return (
|
|
||||||
<span className="text-green-900 font-mono">
|
|
||||||
{/* <span className="select-none opacity-70">{'"'}</span> */}
|
|
||||||
{json}
|
|
||||||
{/* <span className="select-none opacity-70">{'"'}</span> */}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof json === "number") {
|
|
||||||
return <span className="text-purple-500">{json}</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof json === "boolean") {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
json
|
|
||||||
? "text-green-700 bg-green-700/5"
|
|
||||||
: "text-amber-700 bg-amber-500/5",
|
|
||||||
"font-mono",
|
|
||||||
"inline-block px-1 py-0.5 rounded",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{json.toString()}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(json)) {
|
|
||||||
return (
|
|
||||||
<span title={JSON.stringify(json)}>
|
|
||||||
Array <span className="text-gray-500">({json.length})</span>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof json === "object") {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
title={JSON.stringify(json, null, 2)}
|
|
||||||
className="inline-block max-w-64"
|
|
||||||
>
|
|
||||||
{compact ? (
|
|
||||||
<span>
|
|
||||||
Object{" "}
|
|
||||||
<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>
|
|
||||||
) : (
|
|
||||||
<pre className="whitespace-pre-wrap">
|
|
||||||
{JSON.stringify(json, null, 2)}
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <span>{String(json)}</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CoMapPreview = ({
|
|
||||||
coId,
|
|
||||||
node,
|
|
||||||
limit = 6,
|
|
||||||
}: {
|
|
||||||
coId: CoID<RawCoValue>;
|
|
||||||
node: LocalNode;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const { value, snapshot, type, extendedType } = useResolvedCoValue(
|
|
||||||
coId,
|
|
||||||
node,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!snapshot) {
|
|
||||||
return (
|
|
||||||
<div className="rounded bg-gray-100 animate-pulse whitespace-pre w-24">
|
|
||||||
{" "}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshot === "unavailable" && !value) {
|
|
||||||
return <div className="text-gray-500">Unavailable</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extendedType === "image" && isBrowserImage(snapshot)) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<img
|
|
||||||
src={snapshot.placeholderDataURL}
|
|
||||||
className="size-8 border-2 border-white drop-shadow-md my-2"
|
|
||||||
/>
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
{snapshot.originalSize[0]} x {snapshot.originalSize[1]}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* <CoMapPreview coId={value[]} node={node} /> */}
|
|
||||||
{/* <ProgressiveImg image={value}>
|
|
||||||
{({ src }) => <img src={src} className={clsx("w-full")} />}
|
|
||||||
</ProgressiveImg> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extendedType === "record") {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
Record{" "}
|
|
||||||
<span className="text-gray-500">({Object.keys(snapshot).length})</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "colist") {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
List{" "}
|
|
||||||
<span className="text-gray-500">
|
|
||||||
({(snapshot as unknown as []).length})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="text-sm flex flex-col gap-2 items-start">
|
|
||||||
<div className="grid grid-cols-[auto_1fr] gap-2">
|
|
||||||
{Object.entries(snapshot)
|
|
||||||
.slice(0, limit)
|
|
||||||
.map(([key, value]) => (
|
|
||||||
<React.Fragment key={key}>
|
|
||||||
<span className="font-medium">{key}: </span>
|
|
||||||
<span>
|
|
||||||
<ValueRenderer json={value} />
|
|
||||||
</span>
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{Object.entries(snapshot).length > limit && (
|
|
||||||
<div className="text-left text-xs text-gray-500 mt-2">
|
|
||||||
{Object.entries(snapshot).length - limit} more
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AccountOrGroupPreview({
|
|
||||||
coId,
|
|
||||||
node,
|
|
||||||
showId = false,
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
coId: CoID<RawCoValue>;
|
|
||||||
node: LocalNode;
|
|
||||||
showId?: boolean;
|
|
||||||
onClick?: (name?: string) => void;
|
|
||||||
}) {
|
|
||||||
const { snapshot, extendedType } = useResolvedCoValue(coId, node);
|
|
||||||
const [name, setName] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (extendedType === "account") {
|
|
||||||
resolveCoValue(
|
|
||||||
(snapshot as unknown as { profile: CoID<RawCoValue> }).profile,
|
|
||||||
node,
|
|
||||||
).then(({ snapshot }) => {
|
|
||||||
if (
|
|
||||||
typeof snapshot === "object" &&
|
|
||||||
"name" in snapshot &&
|
|
||||||
typeof snapshot.name === "string"
|
|
||||||
) {
|
|
||||||
setName(snapshot.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [snapshot, node, extendedType]);
|
|
||||||
|
|
||||||
if (!snapshot) return <span>Loading...</span>;
|
|
||||||
if (extendedType !== "account" && extendedType !== "group") {
|
|
||||||
return <span>CoID is not an account or group</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayName = extendedType === "account" ? name || "Account" : "Group";
|
|
||||||
const displayText = showId ? `${displayName} (${coId})` : displayName;
|
|
||||||
|
|
||||||
const props = onClick
|
|
||||||
? {
|
|
||||||
onClick: () => onClick(displayName),
|
|
||||||
className: "text-blue-500 cursor-pointer hover:underline",
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
className: "text-gray-500",
|
|
||||||
};
|
|
||||||
|
|
||||||
return <span {...props}>{displayText}</span>;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,30 @@
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
import animate from "tailwindcss-animate";
|
import plugin from "tailwindcss/plugin";
|
||||||
|
|
||||||
|
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>)",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: [
|
content: [
|
||||||
@@ -18,62 +43,26 @@ const config: Config = {
|
|||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
stone: stonePaletteWithAlpha,
|
||||||
input: "hsl(var(--input))",
|
gray: stonePaletteWithAlpha,
|
||||||
ring: "hsl(var(--ring))",
|
blue: {
|
||||||
background: "hsl(var(--background))",
|
50: "#f5f7ff",
|
||||||
foreground: "hsl(var(--foreground))",
|
100: "#ebf0fe",
|
||||||
primary: {
|
200: "#d6e0fd",
|
||||||
DEFAULT: "hsl(var(--primary))",
|
300: "#b3c7fc",
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
400: "#8aa6f9",
|
||||||
|
500: "#5870F1",
|
||||||
|
600: "#3651E7",
|
||||||
|
700: "#3313F7",
|
||||||
|
800: "#2A12BE",
|
||||||
|
900: "#12046A",
|
||||||
|
950: "#1e1b4b",
|
||||||
|
DEFAULT: "#3313F7",
|
||||||
},
|
},
|
||||||
secondary: {
|
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
|
||||||
},
|
|
||||||
destructive: {
|
|
||||||
DEFAULT: "hsl(var(--destructive))",
|
|
||||||
foreground: "hsl(var(--destructive-foreground))",
|
|
||||||
},
|
|
||||||
muted: {
|
|
||||||
DEFAULT: "hsl(var(--muted))",
|
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
|
||||||
},
|
|
||||||
accent: {
|
|
||||||
DEFAULT: "hsl(var(--accent))",
|
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
|
||||||
},
|
|
||||||
popover: {
|
|
||||||
DEFAULT: "hsl(var(--popover))",
|
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
DEFAULT: "hsl(var(--card))",
|
|
||||||
foreground: "hsl(var(--card-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
lg: "var(--radius)",
|
|
||||||
md: "calc(var(--radius) - 2px)",
|
|
||||||
sm: "calc(var(--radius) - 4px)",
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
"accordion-down": {
|
|
||||||
from: { height: "0" },
|
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
},
|
|
||||||
"accordion-up": {
|
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
to: { height: "0" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [animate],
|
plugins: [plugin(({ addVariant }) => addVariant("label", "& :is(label)"))],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# multiauth
|
# multiauth
|
||||||
|
|
||||||
|
## 0.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-react-auth-clerk@0.11.6
|
||||||
|
|
||||||
## 0.0.4
|
## 0.0.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ To run this example, you may either:
|
|||||||
|
|
||||||
1. Create a new Jazz project, and use this example as a template.
|
1. Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example counter --project-name counter
|
npx create-jazz-app@latest counter-app --example counter
|
||||||
```
|
```
|
||||||
2. Navigate to the new project and start the development server.
|
2. Navigate to the new project and start the development server.
|
||||||
```bash
|
```bash
|
||||||
cd counter
|
cd counter-app
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "multiauth",
|
"name": "multiauth",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
<OmniAuth
|
<OmniAuth
|
||||||
sync={{
|
sync={{
|
||||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||||
when: "signedUp", // This makes the app work in local mode when the user is not authenticated
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# jazz-example-musicplayer
|
# jazz-example-musicplayer
|
||||||
|
|
||||||
|
## 0.0.86
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- Updated dependencies [09f0a98]
|
||||||
|
- Updated dependencies [11da4d1]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
- jazz-inspector@0.11.6
|
||||||
|
|
||||||
## 0.0.85
|
## 0.0.85
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example music-player --project-name music-player
|
npx create-jazz-app@latest music-player-app --example music-player
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd music-player
|
cd music-player-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-example-music-player",
|
"name": "jazz-example-music-player",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.85",
|
"version": "0.0.86",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"jazz-inspector": "workspace:*",
|
"jazz-inspector": "workspace:*",
|
||||||
"jazz-react": "workspace:0.11.5",
|
"jazz-react": "workspace:0.11.6",
|
||||||
"jazz-tools": "workspace:0.11.5",
|
"jazz-tools": "workspace:0.11.6",
|
||||||
"lucide-react": "^0.274.0",
|
"lucide-react": "^0.274.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||||||
<JazzProvider
|
<JazzProvider
|
||||||
sync={{
|
sync={{
|
||||||
peer,
|
peer,
|
||||||
when: "signedUp", // This makes the app work in local mode when the user is anonymous
|
|
||||||
}}
|
}}
|
||||||
storage="indexedDB"
|
storage="indexedDB"
|
||||||
AccountSchema={MusicaAccount}
|
AccountSchema={MusicaAccount}
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
|
|||||||
* access rights to CoValues. We get it from the top-level provider `<WithJazz/>`.
|
* access rights to CoValues. We get it from the top-level provider `<WithJazz/>`.
|
||||||
*/
|
*/
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: {
|
resolve: { root: { rootPlaylist: true, playlists: true } },
|
||||||
rootPlaylist: {},
|
|
||||||
playlists: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -51,8 +48,9 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
|
|||||||
|
|
||||||
const params = useParams<{ playlistId: ID<Playlist> }>();
|
const params = useParams<{ playlistId: ID<Playlist> }>();
|
||||||
const playlistId = params.playlistId ?? me?.root._refs.rootPlaylist.id;
|
const playlistId = params.playlistId ?? me?.root._refs.rootPlaylist.id;
|
||||||
|
|
||||||
const playlist = useCoState(Playlist, playlistId, {
|
const playlist = useCoState(Playlist, playlistId, {
|
||||||
tracks: [],
|
resolve: { tracks: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const isRootPlaylist = !params.playlistId;
|
const isRootPlaylist = !params.playlistId;
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ export async function uploadMusicTracks(
|
|||||||
isExampleTrack: boolean = false,
|
isExampleTrack: boolean = false,
|
||||||
) {
|
) {
|
||||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
rootPlaylist: {
|
root: {
|
||||||
tracks: [],
|
rootPlaylist: {
|
||||||
|
tracks: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -65,8 +67,10 @@ export async function uploadMusicTracks(
|
|||||||
|
|
||||||
export async function createNewPlaylist() {
|
export async function createNewPlaylist() {
|
||||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
playlists: [],
|
root: {
|
||||||
|
playlists: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -149,9 +153,11 @@ export async function updateMusicTrackTitle(track: MusicTrack, title: string) {
|
|||||||
|
|
||||||
export async function updateActivePlaylist(playlist?: Playlist) {
|
export async function updateActivePlaylist(playlist?: Playlist) {
|
||||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
activePlaylist: {},
|
root: {
|
||||||
rootPlaylist: {},
|
activePlaylist: true,
|
||||||
|
rootPlaylist: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -160,7 +166,9 @@ export async function updateActivePlaylist(playlist?: Playlist) {
|
|||||||
|
|
||||||
export async function updateActiveTrack(track: MusicTrack) {
|
export async function updateActiveTrack(track: MusicTrack) {
|
||||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {},
|
resolve: {
|
||||||
|
root: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
root.activeTrack = track;
|
root.activeTrack = track;
|
||||||
@@ -170,17 +178,23 @@ export async function onAnonymousAccountDiscarded(
|
|||||||
anonymousAccount: MusicaAccount,
|
anonymousAccount: MusicaAccount,
|
||||||
) {
|
) {
|
||||||
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
rootPlaylist: {
|
root: {
|
||||||
tracks: [{}],
|
rootPlaylist: {
|
||||||
|
tracks: {
|
||||||
|
$each: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
rootPlaylist: {
|
root: {
|
||||||
tracks: [],
|
rootPlaylist: {
|
||||||
|
tracks: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -197,8 +211,10 @@ export async function onAnonymousAccountDiscarded(
|
|||||||
|
|
||||||
export async function deletePlaylist(playlistId: string) {
|
export async function deletePlaylist(playlistId: string) {
|
||||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
playlists: [],
|
root: {
|
||||||
|
playlists: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { getNextTrack, getPrevTrack } from "./lib/getters";
|
|||||||
|
|
||||||
export function useMediaPlayer() {
|
export function useMediaPlayer() {
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: {},
|
resolve: { root: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const playState = usePlayState();
|
const playState = usePlayState();
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ export function InvitePage() {
|
|||||||
const playlist = await Playlist.load(playlistId, {});
|
const playlist = await Playlist.load(playlistId, {});
|
||||||
|
|
||||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
playlists: [],
|
root: {
|
||||||
|
playlists: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,13 @@ export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: {
|
resolve: {
|
||||||
rootPlaylist: {
|
root: {
|
||||||
tracks: [{}],
|
rootPlaylist: {
|
||||||
|
tracks: {
|
||||||
|
$each: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ export function MusicTrackRow({
|
|||||||
const track = useCoState(MusicTrack, trackId);
|
const track = useCoState(MusicTrack, trackId);
|
||||||
|
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: {
|
resolve: { root: { playlists: { $each: true } } },
|
||||||
playlists: [{}],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const playlists = me?.root.playlists ?? [];
|
const playlists = me?.root.playlists ?? [];
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
|
|||||||
const isPlaying = playState.value === "play";
|
const isPlaying = playState.value === "play";
|
||||||
|
|
||||||
const activePlaylist = useAccount({
|
const activePlaylist = useAccount({
|
||||||
root: {
|
resolve: { root: { activePlaylist: true } },
|
||||||
activePlaylist: {},
|
|
||||||
},
|
|
||||||
}).me?.root.activePlaylist;
|
}).me?.root.activePlaylist;
|
||||||
|
|
||||||
useMediaEndListener(mediaPlayer.playNextTrack);
|
useMediaEndListener(mediaPlayer.playNextTrack);
|
||||||
@@ -25,7 +23,7 @@ export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const activeTrack = useCoState(MusicTrack, mediaPlayer.activeTrackId, {
|
const activeTrack = useCoState(MusicTrack, mediaPlayer.activeTrackId, {
|
||||||
waveform: {},
|
resolve: { waveform: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!activeTrack) return null;
|
if (!activeTrack) return null;
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ export function SidePanel() {
|
|||||||
const { playlistId } = useParams();
|
const { playlistId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: {
|
resolve: { root: { playlists: { $each: true } } },
|
||||||
playlists: [{}],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleAllTracksClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
function handleAllTracksClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export function Waveform(props: { track: MusicTrack; height: number }) {
|
|||||||
const waveformData = useCoState(
|
const waveformData = useCoState(
|
||||||
MusicTrackWaveform,
|
MusicTrackWaveform,
|
||||||
track._refs.waveform.id,
|
track._refs.waveform.id,
|
||||||
{},
|
|
||||||
)?.data;
|
)?.data;
|
||||||
const duration = track.duration;
|
const duration = track.duration;
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { MusicaAccount } from "../1_schema";
|
|||||||
|
|
||||||
export async function getNextTrack() {
|
export async function getNextTrack() {
|
||||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
activePlaylist: {
|
root: {
|
||||||
tracks: [],
|
activePlaylist: {
|
||||||
|
tracks: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -21,9 +23,11 @@ export async function getNextTrack() {
|
|||||||
|
|
||||||
export async function getPrevTrack() {
|
export async function getPrevTrack() {
|
||||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {
|
resolve: {
|
||||||
activePlaylist: {
|
root: {
|
||||||
tracks: [],
|
activePlaylist: {
|
||||||
|
tracks: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function useUploadExampleData() {
|
|||||||
|
|
||||||
async function uploadOnboardingData() {
|
async function uploadOnboardingData() {
|
||||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||||
root: {},
|
resolve: { root: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (me.root.exampleDataLoaded) return;
|
if (me.root.exampleDataLoaded) return;
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# organization
|
# organization
|
||||||
|
|
||||||
|
## 0.0.58
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
|
||||||
## 0.0.57
|
## 0.0.57
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example organization --project-name organization
|
npx create-jazz-app@latest organization-app --example organization
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd organization
|
cd organization-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "organization",
|
"name": "organization",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.57",
|
"version": "0.0.58",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import { Organization } from "./schema.ts";
|
|||||||
|
|
||||||
export function AcceptInvitePage() {
|
export function AcceptInvitePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { me } = useAccount({ root: { organizations: [] } });
|
const { me } = useAccount({ resolve: { root: { organizations: true } } });
|
||||||
|
|
||||||
const onAccept = (organizationId: ID<Organization>) => {
|
const onAccept = (organizationId: ID<Organization>) => {
|
||||||
if (me?.root?.organizations) {
|
if (me?.root?.organizations) {
|
||||||
Organization.load(organizationId, me, []).then((organization) => {
|
Organization.load(organizationId).then((organization) => {
|
||||||
if (organization) {
|
if (organization) {
|
||||||
// avoid duplicates
|
// avoid duplicates
|
||||||
const ids = me.root.organizations.map(
|
const ids = me.root.organizations.map(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Heading } from "./components/Heading.tsx";
|
|||||||
|
|
||||||
export function HomePage() {
|
export function HomePage() {
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: { organizations: [{}] },
|
resolve: { root: { organizations: true } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!me?.root.organizations) return;
|
if (!me?.root.organizations) return;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { UserIcon } from "lucide-react";
|
|||||||
|
|
||||||
export function Layout({ children }: { children: React.ReactNode }) {
|
export function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const { me, logOut } = useAccount({
|
const { me, logOut } = useAccount({
|
||||||
root: { draftOrganization: {} },
|
resolve: { root: { draftOrganization: true } },
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function OrganizationPage() {
|
|||||||
.organizationId;
|
.organizationId;
|
||||||
|
|
||||||
const organization = useCoState(Organization, paramOrganizationId, {
|
const organization = useCoState(Organization, paramOrganizationId, {
|
||||||
projects: [],
|
resolve: { projects: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!organization) return <p>Loading organization...</p>;
|
if (!organization) return <p>Loading organization...</p>;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { OrganizationForm } from "./OrganizationForm.tsx";
|
|||||||
|
|
||||||
export function CreateOrganization() {
|
export function CreateOrganization() {
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: { draftOrganization: {}, organizations: [] },
|
resolve: { root: { draftOrganization: true, organizations: true } },
|
||||||
});
|
});
|
||||||
const [errors, setErrors] = useState<string[]>([]);
|
const [errors, setErrors] = useState<string[]>([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Organization } from "../schema.ts";
|
|||||||
|
|
||||||
export function OrganizationSelector({ className }: { className?: string }) {
|
export function OrganizationSelector({ className }: { className?: string }) {
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: { organizations: [{}] },
|
resolve: { root: { organizations: { $each: true } } },
|
||||||
});
|
});
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# passkey-svelte
|
# passkey-svelte
|
||||||
|
|
||||||
|
## 0.0.52
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
|
||||||
|
- jazz-svelte@0.11.6
|
||||||
|
|
||||||
## 0.0.51
|
## 0.0.51
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example passkey-svelte --project-name passkey-svelte
|
npx create-jazz-app@latest passkey-svelte-app --example passkey-svelte
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd passkey-svelte
|
cd passkey-svelte-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "passkey-svelte",
|
"name": "passkey-svelte",
|
||||||
"version": "0.0.51",
|
"version": "0.0.52",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
<JazzProvider
|
<JazzProvider
|
||||||
sync={{
|
sync={{
|
||||||
peer: `wss://cloud.jazz.tools/?key={apiKey}`,
|
peer: `wss://cloud.jazz.tools/?key={apiKey}`,
|
||||||
when: "signedUp",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PasskeyAuthBasicUI appName="minimal-svelte-auth-passkey">
|
<PasskeyAuthBasicUI appName="minimal-svelte-auth-passkey">
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# minimal-auth-passkey
|
# minimal-auth-passkey
|
||||||
|
|
||||||
|
## 0.0.63
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
|
||||||
## 0.0.62
|
## 0.0.62
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ You can either
|
|||||||
|
|
||||||
Create a new Jazz project, and use this example as a template.
|
Create a new Jazz project, and use this example as a template.
|
||||||
```bash
|
```bash
|
||||||
npx create-jazz-app@latest --example passkey --project-name passkey
|
npx create-jazz-app@latest passkey-app --example passkey
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the new project directory.
|
Go to the new project directory.
|
||||||
```bash
|
```bash
|
||||||
cd passkey
|
cd passkey-app
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the dev server.
|
Run the dev server.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "passkey",
|
"name": "passkey",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.62",
|
"version": "0.0.63",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
|||||||
<JazzProvider
|
<JazzProvider
|
||||||
sync={{
|
sync={{
|
||||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||||
when: "signedUp",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PasskeyAuthBasicUI appName="Jazz Minimal Auth Passkey Example">
|
<PasskeyAuthBasicUI appName="Jazz Minimal Auth Passkey Example">
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# passphrase
|
# passphrase
|
||||||
|
|
||||||
|
## 0.0.60
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
|
||||||
|
- Updated dependencies [e7c85b7]
|
||||||
|
- jazz-react@0.11.6
|
||||||
|
- jazz-tools@0.11.6
|
||||||
|
|
||||||
## 0.0.59
|
## 0.0.59
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user