Compare commits
128 Commits
jazz-react
...
jazz-581-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3f56b9be0 | ||
|
|
4cae6bad34 | ||
|
|
17f2ef57de | ||
|
|
3a4d111a37 | ||
|
|
6c94c2cd3d | ||
|
|
98c4221340 | ||
|
|
7b00a8155f | ||
|
|
cc5aea708a | ||
|
|
87c7306632 | ||
|
|
a65219ba52 | ||
|
|
689595fd0a | ||
|
|
efbf2b65a8 | ||
|
|
912232192f | ||
|
|
11b5d77064 | ||
|
|
63b88b591d | ||
|
|
87d0544d98 | ||
|
|
d43dcc2c94 | ||
|
|
093a166db1 | ||
|
|
593f3ed83e | ||
|
|
6c6c89e068 | ||
|
|
c1646f6bbb | ||
|
|
b7deb3c937 | ||
|
|
9b425701e4 | ||
|
|
07dcf54aca | ||
|
|
8ed33c18ca | ||
|
|
6a96d8b53b | ||
|
|
a1bcbda72e | ||
|
|
0f67e0a988 | ||
|
|
6c7a3ebf82 | ||
|
|
27ce186152 | ||
|
|
77ae2cc186 | ||
|
|
66600c0c27 | ||
|
|
81d5261f4d | ||
|
|
d748dea90f | ||
|
|
707e544b83 | ||
|
|
f3cbaee890 | ||
|
|
60ce483db7 | ||
|
|
c08a41398d | ||
|
|
d6d9218b3e | ||
|
|
667e301f12 | ||
|
|
b15f904347 | ||
|
|
0c150a4c74 | ||
|
|
66de2dacb6 | ||
|
|
ae1425db8a | ||
|
|
a164b102f5 | ||
|
|
a6db9a438e | ||
|
|
a35249a831 | ||
|
|
36e246be79 | ||
|
|
b894455c9e | ||
|
|
a462254199 | ||
|
|
34cbdc3e4c | ||
|
|
029c32d86c | ||
|
|
1e18c7f5fc | ||
|
|
45bed3ea80 | ||
|
|
43fa1ecba4 | ||
|
|
141ea11bf1 | ||
|
|
273b478381 | ||
|
|
1d29f74df6 | ||
|
|
393d066b25 | ||
|
|
45bff625c4 | ||
|
|
a8fca7b5f7 | ||
|
|
658a0602ea | ||
|
|
f039e8f097 | ||
|
|
dc9da28bf9 | ||
|
|
e6db8f2a02 | ||
|
|
8c7a6b27ed | ||
|
|
91f96e1188 | ||
|
|
f0c8f7b3eb | ||
|
|
945163b7bc | ||
|
|
3142e503ae | ||
|
|
28dac10723 | ||
|
|
932a21e62a | ||
|
|
628a33eb12 | ||
|
|
20eef64b47 | ||
|
|
c64f38b6c9 | ||
|
|
e5e047660b | ||
|
|
9cb11e38dd | ||
|
|
a65d6bed73 | ||
|
|
f3e4bacb33 | ||
|
|
d4f7891890 | ||
|
|
6eef92b602 | ||
|
|
dfbb4b8c69 | ||
|
|
5f2d8e143c | ||
|
|
240f071951 | ||
|
|
d560b4ddfb | ||
|
|
73d6fd1a85 | ||
|
|
46f5133510 | ||
|
|
b3f2e67d9d | ||
|
|
f689cd20fc | ||
|
|
e22de9ff4c | ||
|
|
735bd41242 | ||
|
|
71998bde62 | ||
|
|
f01b714e29 | ||
|
|
028eca9f81 | ||
|
|
d3d4118b86 | ||
|
|
45f1fe19ac | ||
|
|
ad2db5e6cb | ||
|
|
480890d2e9 | ||
|
|
18428eaaa1 | ||
|
|
5ed31f2678 | ||
|
|
b9d194a80e | ||
|
|
2359e85ebd | ||
|
|
626d43f07b | ||
|
|
a4713df30c | ||
|
|
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
|
||||
@@ -9,12 +9,15 @@
|
||||
"cojson-storage",
|
||||
"cojson-storage-indexeddb",
|
||||
"cojson-storage-sqlite",
|
||||
"cojson-storage-rn-sqlite",
|
||||
"cojson-transport-ws",
|
||||
"jazz-browser",
|
||||
"jazz-auth-clerk",
|
||||
"jazz-browser-media-images",
|
||||
"jazz-inspector",
|
||||
"jazz-nodejs",
|
||||
"jazz-react",
|
||||
"jazz-react-core",
|
||||
"jazz-react-auth-clerk",
|
||||
"jazz-react-native",
|
||||
"jazz-react-native-auth-clerk",
|
||||
|
||||
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/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
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -25,6 +25,9 @@ jobs:
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm exec turbo run build --filter='./packages/*'
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# chat-rn-clerk
|
||||
|
||||
## 1.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.11.1
|
||||
- jazz-react-native-auth-clerk@0.11.1
|
||||
|
||||
## 1.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react-native-media-images@0.11.0
|
||||
- jazz-react-native-auth-clerk@0.11.0
|
||||
- jazz-react-native@0.11.0
|
||||
|
||||
## 1.0.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function Conversation() {
|
||||
const { me } = useAccount();
|
||||
const [chat, setChat] = useState<Chat>();
|
||||
const [message, setMessage] = useState("");
|
||||
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
||||
const loadedChat = useCoState(Chat, chat?.id, { resolve: { $each: true } });
|
||||
const navigation = useNavigation();
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function Conversation() {
|
||||
|
||||
const loadChat = async (chatId: ID<Chat>) => {
|
||||
try {
|
||||
const chat = await Chat.load(chatId, me, []);
|
||||
const chat = await Chat.load(chatId, me);
|
||||
setChat(chat);
|
||||
} catch (error) {
|
||||
console.log("Error loading chat", error);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.80",
|
||||
"version": "1.0.82",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.11.1
|
||||
|
||||
## 1.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react-native@0.11.0
|
||||
|
||||
## 1.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.76",
|
||||
"version": "1.0.78",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Chat, Message } from "./schema";
|
||||
export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
const { me, logOut } = useAccount();
|
||||
const [chatId, setChatId] = useState<ID<Chat>>();
|
||||
const loadedChat = useCoState(Chat, chatId, [{}]);
|
||||
const loadedChat = useCoState(Chat, chatId, { resolve: { $each: true } });
|
||||
const [message, setMessage] = useState("");
|
||||
const profile = useCoState(Profile, me._refs.profile?.id, {});
|
||||
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [18428ea]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-browser@0.11.0
|
||||
- jazz-vue@0.11.0
|
||||
|
||||
## 0.0.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.62",
|
||||
"version": "0.0.63",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -49,7 +49,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const chat = useCoState(Chat, props.chatId, [{}]);
|
||||
const chat = useCoState(Chat, props.chatId, { resolve: { $each: true } });
|
||||
const showNLastMessages = ref(30);
|
||||
|
||||
const displayedMessages = computed(() => {
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.160
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.159
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-browser-media-images@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.158
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.158",
|
||||
"version": "0.0.160",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "./ui.tsx";
|
||||
|
||||
export function ChatScreen(props: { chatID: ID<Chat> }) {
|
||||
const chat = useCoState(Chat, props.chatID, [{}]);
|
||||
const chat = useCoState(Chat, props.chatID, { resolve: { $each: true } });
|
||||
const [showNLastMessages, setShowNLastMessages] = useState(30);
|
||||
|
||||
if (!chat)
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
- jazz-react-auth-clerk@0.11.1
|
||||
|
||||
## 0.0.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react-auth-clerk@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.57",
|
||||
"version": "0.0.59",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -13,7 +13,7 @@
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-react-auth-clerk": "workspace:0.10.15",
|
||||
"jazz-react-auth-clerk": "workspace:0.11.1",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-svelte@0.11.0
|
||||
|
||||
## 0.0.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.42",
|
||||
"version": "0.0.43",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# form
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 18428ea: PasskeyAuth: Sets `profile.name` only if a non-empty username is passed to `signUp`
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-browser-media-images@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.0.53",
|
||||
"version": "0.1.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -12,7 +12,9 @@ import {
|
||||
} from "./schema.ts";
|
||||
|
||||
export function CreateOrder() {
|
||||
const { me } = useAccount({ root: { draft: {}, orders: [] } });
|
||||
const { me } = useAccount({
|
||||
resolve: { root: { draft: true, orders: true } },
|
||||
});
|
||||
const router = useIframeHashRouter();
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
|
||||
@@ -60,7 +62,7 @@ function CreateOrderForm({
|
||||
onSave: (draft: DraftBubbleTeaOrder) => void;
|
||||
}) {
|
||||
const draft = useCoState(DraftBubbleTeaOrder, id, {
|
||||
addOns: [],
|
||||
resolve: { addOns: true },
|
||||
});
|
||||
|
||||
if (!draft) return;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useAccount } from "jazz-react";
|
||||
|
||||
export function DraftIndicator() {
|
||||
const { me } = useAccount({
|
||||
root: { draft: {} },
|
||||
resolve: { root: { draft: true } },
|
||||
});
|
||||
|
||||
if (me?.root.draft?.hasChanges) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { OrderThumbnail } from "./OrderThumbnail.tsx";
|
||||
import { BubbleTeaOrder } from "./schema.ts";
|
||||
|
||||
export function EditOrder(props: { id: ID<BubbleTeaOrder> }) {
|
||||
const order = useCoState(BubbleTeaOrder, props.id, []);
|
||||
const order = useCoState(BubbleTeaOrder, props.id);
|
||||
|
||||
if (!order) return;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { OrderThumbnail } from "./OrderThumbnail.tsx";
|
||||
|
||||
export function Orders() {
|
||||
const { me } = useAccount({
|
||||
root: { orders: [] },
|
||||
resolve: { root: { orders: true } },
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-browser-media-images@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.55",
|
||||
"version": "0.0.57",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.111
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [e22de9f]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [0f67e0a]
|
||||
- cojson@0.11.0
|
||||
- cojson-transport-ws@0.11.0
|
||||
|
||||
## 0.0.110
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector-app",
|
||||
"private": true,
|
||||
"version": "0.0.110",
|
||||
"version": "0.0.111",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,8 +16,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.10.15",
|
||||
"cojson-transport-ws": "workspace:0.10.15",
|
||||
"cojson": "workspace:0.11.0",
|
||||
"cojson-transport-ws": "workspace:0.11.0",
|
||||
"hash-slash": "workspace:0.2.2",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7b00a81]
|
||||
- jazz-inspector@0.11.1
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [b7deb3c]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
- jazz-inspector@0.10.13
|
||||
|
||||
## 0.0.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.79",
|
||||
"version": "0.0.81",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -22,8 +22,8 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:0.10.15",
|
||||
"jazz-tools": "workspace:0.10.15",
|
||||
"jazz-react": "workspace:0.11.1",
|
||||
"jazz-tools": "workspace:0.11.0",
|
||||
"lucide-react": "^0.274.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
@@ -24,10 +24,7 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
|
||||
* access rights to CoValues. We get it from the top-level provider `<WithJazz/>`.
|
||||
*/
|
||||
const { me } = useAccount({
|
||||
root: {
|
||||
rootPlaylist: {},
|
||||
playlists: [],
|
||||
},
|
||||
resolve: { root: { rootPlaylist: true, playlists: true } },
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
@@ -51,8 +48,9 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
|
||||
|
||||
const params = useParams<{ playlistId: ID<Playlist> }>();
|
||||
const playlistId = params.playlistId ?? me?.root._refs.rootPlaylist.id;
|
||||
|
||||
const playlist = useCoState(Playlist, playlistId, {
|
||||
tracks: [],
|
||||
resolve: { tracks: true },
|
||||
});
|
||||
|
||||
const isRootPlaylist = !params.playlistId;
|
||||
|
||||
@@ -27,9 +27,11 @@ export async function uploadMusicTracks(
|
||||
isExampleTrack: boolean = false,
|
||||
) {
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [],
|
||||
resolve: {
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -65,8 +67,10 @@ export async function uploadMusicTracks(
|
||||
|
||||
export async function createNewPlaylist() {
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
playlists: [],
|
||||
resolve: {
|
||||
root: {
|
||||
playlists: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -112,34 +116,31 @@ export async function addTrackToPlaylist(
|
||||
playlist.tracks?.push(track);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Since musicTracks are created as private values (see uploadMusicTracks)
|
||||
* to make them shareable as part of the playlist we are cloning them
|
||||
* and setting the playlist group as owner of the clone
|
||||
*
|
||||
* Doing this for backwards compatibility for when the Group inheritance wasn't possible
|
||||
*/
|
||||
const blob = await FileStream.loadAsBlob(track._refs.file.id);
|
||||
const waveform = await MusicTrackWaveform.load(track._refs.waveform.id, {});
|
||||
|
||||
if (!blob || !waveform) return;
|
||||
|
||||
const trackClone = MusicTrack.create(
|
||||
{
|
||||
file: await FileStream.createFromBlob(blob, playlist._owner),
|
||||
duration: track.duration,
|
||||
waveform: MusicTrackWaveform.create(
|
||||
{ data: waveform.data },
|
||||
playlist._owner,
|
||||
),
|
||||
title: track.title,
|
||||
sourceTrack: track,
|
||||
},
|
||||
playlist._owner,
|
||||
export async function removeTrackFromPlaylist(
|
||||
playlist: Playlist,
|
||||
track: MusicTrack,
|
||||
) {
|
||||
const notAdded = !playlist.tracks?.some(
|
||||
(t) => t?.id === track.id || t?._refs.sourceTrack?.id === track.id,
|
||||
);
|
||||
|
||||
playlist.tracks?.push(trackClone);
|
||||
if (notAdded) return;
|
||||
|
||||
if (track._owner._type === "Group" && playlist._owner._type === "Group") {
|
||||
const trackGroup = track._owner;
|
||||
await trackGroup.revokeExtend(playlist._owner);
|
||||
|
||||
const index =
|
||||
playlist.tracks?.findIndex(
|
||||
(t) => t?.id === track.id || t?._refs.sourceTrack?.id === track.id,
|
||||
) ?? -1;
|
||||
if (index > -1) {
|
||||
playlist.tracks?.splice(index, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePlaylistTitle(playlist: Playlist, title: string) {
|
||||
@@ -152,9 +153,11 @@ export async function updateMusicTrackTitle(track: MusicTrack, title: string) {
|
||||
|
||||
export async function updateActivePlaylist(playlist?: Playlist) {
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
activePlaylist: {},
|
||||
rootPlaylist: {},
|
||||
resolve: {
|
||||
root: {
|
||||
activePlaylist: true,
|
||||
rootPlaylist: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -163,7 +166,9 @@ export async function updateActivePlaylist(playlist?: Playlist) {
|
||||
|
||||
export async function updateActiveTrack(track: MusicTrack) {
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {},
|
||||
resolve: {
|
||||
root: {},
|
||||
},
|
||||
});
|
||||
|
||||
root.activeTrack = track;
|
||||
@@ -173,17 +178,23 @@ export async function onAnonymousAccountDiscarded(
|
||||
anonymousAccount: MusicaAccount,
|
||||
) {
|
||||
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [{}],
|
||||
resolve: {
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: {
|
||||
$each: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [],
|
||||
resolve: {
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getNextTrack, getPrevTrack } from "./lib/getters";
|
||||
|
||||
export function useMediaPlayer() {
|
||||
const { me } = useAccount({
|
||||
root: {},
|
||||
resolve: { root: true },
|
||||
});
|
||||
|
||||
const playState = usePlayState();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useAcceptInvite } from "jazz-react";
|
||||
import { useAcceptInvite, useIsAuthenticated } from "jazz-react";
|
||||
import { ID } from "jazz-tools";
|
||||
import { useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -7,6 +7,8 @@ import { MusicaAccount, Playlist } from "./1_schema";
|
||||
export function InvitePage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: Playlist,
|
||||
onAccept: useCallback(
|
||||
@@ -14,8 +16,10 @@ export function InvitePage() {
|
||||
const playlist = await Playlist.load(playlistId, {});
|
||||
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
playlists: [],
|
||||
resolve: {
|
||||
root: {
|
||||
playlists: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,5 +36,9 @@ export function InvitePage() {
|
||||
),
|
||||
});
|
||||
|
||||
return <p>Accepting invite....</p>;
|
||||
return isAuthenticated ? (
|
||||
<p>Accepting invite....</p>
|
||||
) : (
|
||||
<p>Please sign in to accept the invite.</p>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,13 @@ export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const { me } = useAccount({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [{}],
|
||||
resolve: {
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: {
|
||||
$each: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MusicTrack, Playlist } from "@/1_schema";
|
||||
import { addTrackToPlaylist } from "@/4_actions";
|
||||
import { addTrackToPlaylist, removeTrackFromPlaylist } from "@/4_actions";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -10,6 +10,7 @@ import { cn } from "@/lib/utils";
|
||||
import { useAccount, useCoState } from "jazz-react";
|
||||
import { ID } from "jazz-tools";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
import { Fragment } from "react/jsx-runtime";
|
||||
import { MusicTrackTitleInput } from "./MusicTrackTitleInput";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
@@ -29,9 +30,7 @@ export function MusicTrackRow({
|
||||
const track = useCoState(MusicTrack, trackId);
|
||||
|
||||
const { me } = useAccount({
|
||||
root: {
|
||||
playlists: [{}],
|
||||
},
|
||||
resolve: { root: { playlists: { $each: true } } },
|
||||
});
|
||||
|
||||
const playlists = me?.root.playlists ?? [];
|
||||
@@ -46,6 +45,11 @@ export function MusicTrackRow({
|
||||
addTrackToPlaylist(playlist, track);
|
||||
}
|
||||
|
||||
function handleRemoveFromPlaylist(playlist: Playlist) {
|
||||
if (!track) return;
|
||||
removeTrackFromPlaylist(playlist, track);
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
className={
|
||||
@@ -85,12 +89,20 @@ export function MusicTrackRow({
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{playlists.map((playlist, index) => (
|
||||
<DropdownMenuItem
|
||||
key={index}
|
||||
onSelect={() => handleAddToPlaylist(playlist)}
|
||||
>
|
||||
Add to {playlist.title}
|
||||
</DropdownMenuItem>
|
||||
<Fragment key={index}>
|
||||
<DropdownMenuItem
|
||||
key={`add-${index}`}
|
||||
onSelect={() => handleAddToPlaylist(playlist)}
|
||||
>
|
||||
Add to {playlist.title}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
key={`remove-${index}`}
|
||||
onSelect={() => handleRemoveFromPlaylist(playlist)}
|
||||
>
|
||||
Remove from {playlist.title}
|
||||
</DropdownMenuItem>
|
||||
</Fragment>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -12,9 +12,7 @@ export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
|
||||
const isPlaying = playState.value === "play";
|
||||
|
||||
const activePlaylist = useAccount({
|
||||
root: {
|
||||
activePlaylist: {},
|
||||
},
|
||||
resolve: { root: { activePlaylist: true } },
|
||||
}).me?.root.activePlaylist;
|
||||
|
||||
useMediaEndListener(mediaPlayer.playNextTrack);
|
||||
@@ -25,7 +23,7 @@ export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
|
||||
});
|
||||
|
||||
const activeTrack = useCoState(MusicTrack, mediaPlayer.activeTrackId, {
|
||||
waveform: {},
|
||||
resolve: { waveform: true },
|
||||
});
|
||||
|
||||
if (!activeTrack) return null;
|
||||
|
||||
@@ -6,9 +6,7 @@ export function SidePanel() {
|
||||
const { playlistId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { me } = useAccount({
|
||||
root: {
|
||||
playlists: [{}],
|
||||
},
|
||||
resolve: { root: { playlists: { $each: true } } },
|
||||
});
|
||||
|
||||
function handleAllTracksClick(evt: React.MouseEvent<HTMLAnchorElement>) {
|
||||
|
||||
@@ -8,7 +8,6 @@ export function Waveform(props: { track: MusicTrack; height: number }) {
|
||||
const waveformData = useCoState(
|
||||
MusicTrackWaveform,
|
||||
track._refs.waveform.id,
|
||||
{},
|
||||
)?.data;
|
||||
const duration = track.duration;
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ import { MusicaAccount } from "../1_schema";
|
||||
|
||||
export async function getNextTrack() {
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
activePlaylist: {
|
||||
tracks: [],
|
||||
resolve: {
|
||||
root: {
|
||||
activePlaylist: {
|
||||
tracks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -21,9 +23,11 @@ export async function getNextTrack() {
|
||||
|
||||
export async function getPrevTrack() {
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
activePlaylist: {
|
||||
tracks: [],
|
||||
resolve: {
|
||||
root: {
|
||||
activePlaylist: {
|
||||
tracks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ export function useUploadExampleData() {
|
||||
|
||||
async function uploadOnboardingData() {
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {},
|
||||
resolve: { root: true },
|
||||
});
|
||||
|
||||
if (me.root.exampleDataLoaded) return;
|
||||
|
||||
@@ -82,3 +82,47 @@ test("create a new playlist and share", async ({
|
||||
await luigiHome.playMusicTrack("Super Mario World");
|
||||
await luigiHome.expectActiveTrackPlaying();
|
||||
});
|
||||
|
||||
test("create a new playlist, share, then remove track", async ({
|
||||
page: marioPage,
|
||||
browser,
|
||||
}) => {
|
||||
// Create playlist with a song and share
|
||||
await marioPage.goto("/");
|
||||
const marioHome = new HomePage(marioPage);
|
||||
await marioHome.expectMusicTrack("Example song");
|
||||
await marioHome.editTrackTitle("Example song", "Super Mario World");
|
||||
await marioHome.createPlaylist();
|
||||
await marioHome.editPlaylistTitle("Save the princess");
|
||||
await marioHome.navigateToPlaylist("All tracks");
|
||||
await marioHome.addTrackToPlaylist("Super Mario World", "Save the princess");
|
||||
await marioHome.navigateToPlaylist("Save the princess");
|
||||
await marioHome.expectMusicTrack("Super Mario World");
|
||||
await marioHome.signUp("Mario");
|
||||
const url = await marioHome.getShareLink();
|
||||
|
||||
await sleep(4000); // Wait for the sync to complete
|
||||
|
||||
// Retrieve shared playlist
|
||||
const luigiContext = await browser.newContext();
|
||||
await mockAuthenticator(luigiContext);
|
||||
const luigiPage = await luigiContext.newPage();
|
||||
await luigiPage.goto("/");
|
||||
const luigiHome = new HomePage(luigiPage);
|
||||
await luigiHome.signUp("Luigi");
|
||||
await luigiPage.goto(url);
|
||||
await luigiHome.expectMusicTrack("Super Mario World");
|
||||
|
||||
// Remove track from playlist
|
||||
await marioHome.navigateToHome();
|
||||
await marioHome.removeTrackFromPlaylist(
|
||||
"Super Mario World",
|
||||
"Save the princess",
|
||||
);
|
||||
await sleep(4000); // Wait for the sync to complete
|
||||
|
||||
// Expect that the track is removed from the playlist
|
||||
await marioHome.navigateToPlaylist("Save the princess");
|
||||
await marioHome.notExpectMusicTrack("Super Mario World");
|
||||
await luigiHome.notExpectMusicTrack("Super Mario World");
|
||||
});
|
||||
|
||||
@@ -33,6 +33,14 @@ export class HomePage {
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
async notExpectMusicTrack(trackName: string) {
|
||||
await expect(
|
||||
this.page.getByRole("button", {
|
||||
name: `Play ${trackName}`,
|
||||
}),
|
||||
).not.toBeVisible();
|
||||
}
|
||||
|
||||
async playMusicTrack(trackName: string) {
|
||||
await this.page
|
||||
.getByRole("button", {
|
||||
@@ -65,6 +73,14 @@ export class HomePage {
|
||||
.click();
|
||||
}
|
||||
|
||||
async navigateToHome() {
|
||||
await this.page
|
||||
.getByRole("link", {
|
||||
name: "All tracks",
|
||||
})
|
||||
.click();
|
||||
}
|
||||
|
||||
async getShareLink() {
|
||||
await this.page
|
||||
.getByRole("button", {
|
||||
@@ -95,6 +111,20 @@ export class HomePage {
|
||||
.click();
|
||||
}
|
||||
|
||||
async removeTrackFromPlaylist(trackTitle: string, playlistTitle: string) {
|
||||
await this.page
|
||||
.getByRole("button", {
|
||||
name: `Open ${trackTitle} menu`,
|
||||
})
|
||||
.click();
|
||||
|
||||
await this.page
|
||||
.getByRole("menuitem", {
|
||||
name: `Remove from ${playlistTitle}`,
|
||||
})
|
||||
.click();
|
||||
}
|
||||
|
||||
async signUp(name: string) {
|
||||
await this.page.getByRole("button", { name: "Sign up" }).click();
|
||||
await this.page.getByRole("textbox", { name: "Username" }).fill(name);
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# organization
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.51",
|
||||
"version": "0.0.53",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -5,11 +5,11 @@ import { Organization } from "./schema.ts";
|
||||
|
||||
export function AcceptInvitePage() {
|
||||
const navigate = useNavigate();
|
||||
const { me } = useAccount({ root: { organizations: [] } });
|
||||
const { me } = useAccount({ resolve: { root: { organizations: true } } });
|
||||
|
||||
const onAccept = (organizationId: ID<Organization>) => {
|
||||
if (me?.root?.organizations) {
|
||||
Organization.load(organizationId, me, []).then((organization) => {
|
||||
Organization.load(organizationId).then((organization) => {
|
||||
if (organization) {
|
||||
// avoid duplicates
|
||||
const ids = me.root.organizations.map(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Heading } from "./components/Heading.tsx";
|
||||
|
||||
export function HomePage() {
|
||||
const { me } = useAccount({
|
||||
root: { organizations: [{}] },
|
||||
resolve: { root: { organizations: true } },
|
||||
});
|
||||
|
||||
if (!me?.root.organizations) return;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UserIcon } from "lucide-react";
|
||||
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
const { me, logOut } = useAccount({
|
||||
root: { draftOrganization: {} },
|
||||
resolve: { root: { draftOrganization: true } },
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,7 +13,7 @@ export function OrganizationPage() {
|
||||
.organizationId;
|
||||
|
||||
const organization = useCoState(Organization, paramOrganizationId, {
|
||||
projects: [],
|
||||
resolve: { projects: true },
|
||||
});
|
||||
|
||||
if (!organization) return <p>Loading organization...</p>;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { OrganizationForm } from "./OrganizationForm.tsx";
|
||||
|
||||
export function CreateOrganization() {
|
||||
const { me } = useAccount({
|
||||
root: { draftOrganization: {}, organizations: [] },
|
||||
resolve: { root: { draftOrganization: true, organizations: true } },
|
||||
});
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
const navigate = useNavigate();
|
||||
@@ -23,7 +23,7 @@ export function CreateOrganization() {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = Group.create({ owner: me });
|
||||
const group = Group.create();
|
||||
|
||||
me.root.organizations.push(draft as Organization);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCoState } from "jazz-react";
|
||||
import { Account, Group, ID } from "jazz-tools";
|
||||
import { Group } from "jazz-tools";
|
||||
import { Organization } from "../schema.ts";
|
||||
|
||||
export function OrganizationMembers({
|
||||
@@ -10,26 +9,13 @@ export function OrganizationMembers({
|
||||
return (
|
||||
<>
|
||||
{group.members.map((member) => (
|
||||
<Member
|
||||
key={member.id}
|
||||
accountId={member.id as ID<Account>}
|
||||
role={member.role}
|
||||
/>
|
||||
<div key={member.id} className="px-4 py-5 sm:px-6">
|
||||
<strong className="font-medium">
|
||||
{member.account.profile?.name}
|
||||
</strong>{" "}
|
||||
({member.role})
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
function Member({
|
||||
accountId,
|
||||
role,
|
||||
}: { accountId: ID<Account>; role?: string }) {
|
||||
const account = useCoState(Account, accountId, { profile: {} });
|
||||
|
||||
if (!account?.profile) return;
|
||||
|
||||
return (
|
||||
<div className="px-4 py-5 sm:px-6">
|
||||
<strong className="font-medium">{account.profile.name}</strong> ({role})
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Organization } from "../schema.ts";
|
||||
|
||||
export function OrganizationSelector({ className }: { className?: string }) {
|
||||
const { me } = useAccount({
|
||||
root: { organizations: [{}] },
|
||||
resolve: { root: { organizations: { $each: true } } },
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.11.0
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.46",
|
||||
"version": "0.0.47",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.56",
|
||||
"version": "0.0.58",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# passphrase
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.53",
|
||||
"version": "0.0.55",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.77",
|
||||
"version": "0.0.79",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,8 +12,8 @@
|
||||
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-react": "workspace:0.10.15",
|
||||
"jazz-tools": "workspace:0.10.15",
|
||||
"jazz-react": "workspace:0.11.1",
|
||||
"jazz-tools": "workspace:0.11.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.41.5",
|
||||
|
||||
@@ -6,7 +6,7 @@ import NewItemModal from "./components/new-item-modal";
|
||||
import Table from "./components/table";
|
||||
|
||||
import { useAccount, useCoState } from "jazz-react";
|
||||
import { CoMapInit, Group, ID } from "jazz-tools";
|
||||
import { CoMapInit, ID } from "jazz-tools";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Folder, FolderList, PasswordItem } from "./1_schema";
|
||||
import {
|
||||
@@ -43,9 +43,11 @@ const VaultPage: React.FC = () => {
|
||||
(item): item is Exclude<typeof item, null> => !!item,
|
||||
) || [],
|
||||
);
|
||||
const folders = useCoState(FolderList, me.root?._refs.folders?.id, [
|
||||
{ items: [{}] },
|
||||
]);
|
||||
const folders = useCoState(FolderList, me.root?._refs.folders?.id, {
|
||||
resolve: {
|
||||
$each: { items: { $each: true } },
|
||||
},
|
||||
});
|
||||
|
||||
const [selectedFolder, setSelectedFolder] = useState<Folder | undefined>();
|
||||
const [isNewItemModalOpen, setIsNewItemModalOpen] = useState(false);
|
||||
@@ -136,20 +138,14 @@ const VaultPage: React.FC = () => {
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setEditingItem(item)}
|
||||
disabled={
|
||||
item._owner.castAs(Group).myRole() !== "admin" &&
|
||||
item._owner.castAs(Group).myRole() !== "writer"
|
||||
}
|
||||
disabled={!me.canWrite(item)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDeleteItem(item)}
|
||||
variant="danger"
|
||||
disabled={
|
||||
item._owner.castAs(Group).myRole() !== "admin" &&
|
||||
item._owner.castAs(Group).myRole() !== "writer"
|
||||
}
|
||||
disabled={!me.canWrite(item)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
@@ -210,21 +206,13 @@ const VaultPage: React.FC = () => {
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => setIsNewItemModalOpen(true)}
|
||||
disabled={
|
||||
!selectedFolder ||
|
||||
(selectedFolder._owner.castAs(Group).myRole() !== "admin" &&
|
||||
selectedFolder._owner.castAs(Group).myRole() !== "writer")
|
||||
}
|
||||
disabled={!selectedFolder || !me.canWrite(selectedFolder)}
|
||||
>
|
||||
New Item
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setIsInviteModalOpen(true)}
|
||||
disabled={
|
||||
!selectedFolder ||
|
||||
(selectedFolder._owner.castAs(Group).myRole() !== "admin" &&
|
||||
selectedFolder._owner.castAs(Group).myRole() !== "writer")
|
||||
}
|
||||
disabled={!selectedFolder || !me.canWrite(selectedFolder)}
|
||||
>
|
||||
Share Folder
|
||||
</Button>
|
||||
|
||||
@@ -60,11 +60,9 @@ export async function addSharedFolder(
|
||||
me: PasswordManagerAccount,
|
||||
) {
|
||||
const [sharedFolder, account] = await Promise.all([
|
||||
Folder.load(sharedFolderId, me, {}),
|
||||
PasswordManagerAccount.load(me.id, me, {
|
||||
root: {
|
||||
folders: [],
|
||||
},
|
||||
Folder.load(sharedFolderId),
|
||||
PasswordManagerAccount.load(me.id, {
|
||||
resolve: { root: { folders: true } },
|
||||
}),
|
||||
]);
|
||||
|
||||
|
||||
@@ -21,11 +21,9 @@ const InviteModal: React.FC<InviteModalProps> = ({
|
||||
>("reader");
|
||||
const [inviteLink, setInviteLink] = useState("");
|
||||
|
||||
const members = selectedFolder?._owner.castAs(Group).members;
|
||||
const members = selectedFolder?._owner.members;
|
||||
const invitedMembers = members
|
||||
? members
|
||||
.filter((m) => !m.account?.isMe && m.role !== "revoked")
|
||||
.map((m) => m.account)
|
||||
? members.filter((m) => !m.account?.isMe).map((m) => m.account)
|
||||
: [];
|
||||
|
||||
const handleCreateInviteLink = () => {
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.177
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.176
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-browser-media-images@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.175
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.175",
|
||||
"version": "0.0.177",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -19,9 +19,9 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.10.15",
|
||||
"jazz-react": "workspace:0.10.15",
|
||||
"jazz-tools": "workspace:0.10.15",
|
||||
"jazz-browser-media-images": "workspace:0.11.0",
|
||||
"jazz-react": "workspace:0.11.1",
|
||||
"jazz-tools": "workspace:0.11.0",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -41,7 +41,7 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-run": "workspace:0.10.15",
|
||||
"jazz-run": "workspace:0.11.0",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
|
||||
@@ -34,7 +34,7 @@ export function NewPetPostForm() {
|
||||
if (newPetPost) {
|
||||
newPetPost.name = name;
|
||||
} else {
|
||||
const petPostGroup = Group.create({ owner: me });
|
||||
const petPostGroup = Group.create();
|
||||
const petPost = PartialPetPost.create(
|
||||
{
|
||||
name,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useParams } from "react-router";
|
||||
|
||||
import { PetPost, PetReactions, ReactionTypes } from "./1_schema";
|
||||
|
||||
import { ProgressiveImg } from "jazz-react";
|
||||
import { ProgressiveImg, useAccount } from "jazz-react";
|
||||
import { useCoState } from "jazz-react";
|
||||
import { ID } from "jazz-tools";
|
||||
import uniqolor from "uniqolor";
|
||||
@@ -26,6 +26,7 @@ const reactionEmojiMap: {
|
||||
export function RatePetPostUI() {
|
||||
const petPostID = useParams<{ petPostId: ID<PetPost> }>().petPostId;
|
||||
|
||||
const { me } = useAccount();
|
||||
const petPost = useCoState(PetPost, petPostID);
|
||||
|
||||
return (
|
||||
@@ -60,7 +61,7 @@ export function RatePetPostUI() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{petPost?._owner.myRole() === "admin" && petPost.reactions && (
|
||||
{petPost && me.canAdmin(petPost) && petPost.reactions && (
|
||||
<ReactionOverview petReactions={petPost.reactions} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
||||
|
||||
import { PetPost } from "../1_schema";
|
||||
|
||||
import { createInviteLink } from "jazz-react";
|
||||
import { createInviteLink, useAccount } from "jazz-react";
|
||||
import QRCode from "qrcode";
|
||||
|
||||
import { Button, useToast } from "../basicComponents";
|
||||
@@ -10,9 +10,11 @@ import { Button, useToast } from "../basicComponents";
|
||||
export function ShareButton({ petPost }: { petPost?: PetPost | null }) {
|
||||
const [existingInviteLink, setExistingInviteLink] = useState<string>();
|
||||
const { toast } = useToast();
|
||||
const { me } = useAccount();
|
||||
|
||||
return (
|
||||
petPost?._owner.myRole() === "admin" && (
|
||||
petPost &&
|
||||
me.canAdmin(petPost) && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="py-0"
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-browser-media-images@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.55",
|
||||
"version": "0.0.57",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -9,8 +9,7 @@ function App() {
|
||||
const router = useIframeHashRouter();
|
||||
|
||||
const createReactions = () => {
|
||||
if (!me) return;
|
||||
const group = Group.create({ owner: me });
|
||||
const group = Group.create();
|
||||
group.addMember("everyone", "writer");
|
||||
const chat = Reactions.create([], { owner: group });
|
||||
router.navigate("/#/reactions/" + chat.id);
|
||||
|
||||
@@ -14,7 +14,7 @@ const reactionEmojiMap: {
|
||||
};
|
||||
|
||||
export function ReactionsScreen(props: { id: ID<Reactions> }) {
|
||||
const reactions = useCoState(Reactions, props.id, []);
|
||||
const reactions = useCoState(Reactions, props.id);
|
||||
|
||||
if (!reactions) return;
|
||||
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [18428ea]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-browser@0.11.0
|
||||
- jazz-vue@0.11.0
|
||||
|
||||
## 0.0.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.60",
|
||||
"version": "0.0.61",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
<div class="section-header">
|
||||
<h2>Folders</h2>
|
||||
<div class="new-folder">
|
||||
<input
|
||||
v-model="newFolderName"
|
||||
placeholder="New folder name"
|
||||
<input
|
||||
v-model="newFolderName"
|
||||
placeholder="New folder name"
|
||||
class="input"
|
||||
/>
|
||||
<button class="btn btn-primary" @click="createFolder">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="folder-list">
|
||||
<div
|
||||
v-for="folder in folders"
|
||||
@@ -32,9 +32,9 @@
|
||||
<div class="section-header">
|
||||
<h2>{{ selectedFolder?.name }}</h2>
|
||||
<div class="new-todo">
|
||||
<input
|
||||
v-model="newTodoTitle"
|
||||
placeholder="Add a new task"
|
||||
<input
|
||||
v-model="newTodoTitle"
|
||||
placeholder="Add a new task"
|
||||
class="input"
|
||||
/>
|
||||
<button class="btn btn-primary" @click="createTodo">Add</button>
|
||||
@@ -72,7 +72,9 @@ import { Folder, FolderList, ToDoItem, ToDoList } from "../schema";
|
||||
const { me } = useAccount();
|
||||
|
||||
const computedFoldersId = computed(() => me.value?.root?.folders?.id);
|
||||
const folders = useCoState(FolderList, computedFoldersId, [{ items: [{}] }]);
|
||||
const folders = useCoState(FolderList, computedFoldersId, {
|
||||
resolve: { $each: { items: true } },
|
||||
});
|
||||
|
||||
const selectedFolder = ref<Folder>();
|
||||
const newFolderName = ref("");
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.176
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.175
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.174
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.174",
|
||||
"version": "0.0.176",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,8 +16,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "workspace:0.10.15",
|
||||
"jazz-tools": "workspace:0.10.15",
|
||||
"jazz-react": "workspace:0.11.1",
|
||||
"jazz-tools": "workspace:0.11.0",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function App() {
|
||||
|
||||
function HomeScreen() {
|
||||
const { me } = useAccount({
|
||||
root: { projects: [{}] },
|
||||
resolve: { root: { projects: { $each: true } } },
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export function NewProjectForm() {
|
||||
// To create a new todo project, we first create a `Group`,
|
||||
// which is a scope for defining access rights (reader/writer/admin)
|
||||
// of its members, which will apply to all CoValues owned by that group.
|
||||
const projectGroup = Group.create({ owner: me });
|
||||
const projectGroup = Group.create();
|
||||
|
||||
// Then we create an empty todo project within that group
|
||||
const project = TodoProject.create(
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
||||
|
||||
import QRCode from "qrcode";
|
||||
|
||||
import { createInviteLink } from "jazz-react";
|
||||
import { createInviteLink, useAccount } from "jazz-react";
|
||||
import { CoValue } from "jazz-tools";
|
||||
import { Button, useToast } from "../basicComponents";
|
||||
|
||||
@@ -15,9 +15,11 @@ export function InviteButton<T extends CoValue>({
|
||||
}) {
|
||||
const [existingInviteLink, setExistingInviteLink] = useState<string>();
|
||||
const { toast } = useToast();
|
||||
const { me } = useAccount();
|
||||
|
||||
return (
|
||||
value?._owner?.myRole() === "admin" && (
|
||||
value &&
|
||||
me.canAdmin(value) && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="py-0"
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.11.1
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6a96d8b]
|
||||
- Updated dependencies [a35249a]
|
||||
- Updated dependencies [b9d194a]
|
||||
- Updated dependencies [a4713df]
|
||||
- Updated dependencies [34cbdc3]
|
||||
- Updated dependencies [f039e8f]
|
||||
- Updated dependencies [e22de9f]
|
||||
- jazz-tools@0.11.0
|
||||
- jazz-react@0.11.0
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.52",
|
||||
"version": "0.0.54",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -15,7 +15,7 @@ function App() {
|
||||
const issue = useCoState(Issue, issueID);
|
||||
|
||||
const createIssue = () => {
|
||||
const group = Group.create({ owner: me });
|
||||
const group = Group.create();
|
||||
group.addMember("everyone", "writer");
|
||||
|
||||
const newIssue = Issue.create(
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { createInviteLink } from "jazz-react";
|
||||
import { createInviteLink, useAccount } from "jazz-react";
|
||||
import { useCoState } from "jazz-react";
|
||||
import { ID } from "jazz-tools";
|
||||
import { IssueComponent } from "./Issue.tsx";
|
||||
import { Issue, Project } from "./schema.ts";
|
||||
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {
|
||||
const project = useCoState(Project, projectID, { issues: [{}] });
|
||||
const project = useCoState(Project, projectID, {
|
||||
resolve: { issues: { $each: true } },
|
||||
});
|
||||
|
||||
if (!project) return;
|
||||
|
||||
@@ -21,14 +23,15 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {
|
||||
estimate: 0,
|
||||
status: "backlog",
|
||||
},
|
||||
{ owner: project._owner },
|
||||
project._owner,
|
||||
),
|
||||
);
|
||||
};
|
||||
const { me } = useAccount();
|
||||
return project ? (
|
||||
<div>
|
||||
<h1>{project.name}</h1>
|
||||
{project._owner?.myRole() === "admin" && (
|
||||
{me.canAdmin(project) && (
|
||||
<>
|
||||
<button onClick={() => invite("reader")}>Invite Guest</button>
|
||||
<button onClick={() => invite("writer")}>Invite Member</button>
|
||||
|
||||
49
homepage/design-system/src/app/components/atoms/Alert.tsx
Normal file
49
homepage/design-system/src/app/components/atoms/Alert.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import clsx from "clsx";
|
||||
import type { ReactNode } from "react";
|
||||
import { Icon } from "./Icon";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
variant?: "warning" | "info";
|
||||
title: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Alert({
|
||||
children,
|
||||
variant = "warning",
|
||||
title,
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"border-l-4 p-4 pl-6 dark:bg-red-200/5 overflow-hidden relative rounded",
|
||||
{
|
||||
"border-yellow-400 bg-yellow-50 dark:border-yellow-500 dark:bg-yellow-200/5":
|
||||
variant === "warning",
|
||||
"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-200/5":
|
||||
variant === "info",
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
"text-sm font-bold flex items-center gap-1",
|
||||
variant === "warning" && "text-yellow-700 dark:text-yellow-400",
|
||||
variant === "info" && "text-blue-700 dark:text-blue-400",
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
name={variant}
|
||||
size="7xl"
|
||||
className="absolute -z-10 right-0 opacity-5 top-0 rotate-12 pointer-events-none"
|
||||
/>
|
||||
<Icon name={variant} size="xs" />
|
||||
{title}
|
||||
</span>
|
||||
<span className={clsx("text-sm")}>{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import {
|
||||
AlertCircleIcon,
|
||||
AlertTriangleIcon,
|
||||
ArrowDownIcon,
|
||||
ArrowRightIcon,
|
||||
BookTextIcon,
|
||||
@@ -16,9 +18,10 @@ import {
|
||||
GlobeIcon,
|
||||
HashIcon,
|
||||
ImageIcon,
|
||||
InfoIcon,
|
||||
LinkIcon,
|
||||
LockKeyholeIcon,
|
||||
LucideIcon,
|
||||
type LucideIcon,
|
||||
MailIcon,
|
||||
MenuIcon,
|
||||
MessageCircleQuestionIcon,
|
||||
@@ -71,6 +74,8 @@ const icons = {
|
||||
touchId: FingerprintIcon,
|
||||
upload: UploadCloudIcon,
|
||||
zip: FolderArchiveIcon,
|
||||
warning: AlertTriangleIcon,
|
||||
info: InfoIcon,
|
||||
};
|
||||
|
||||
// copied from tailwind line height https://tailwindcss.com/docs/font-size
|
||||
|
||||
@@ -133,6 +133,36 @@ teamGroup.extend(companyGroup);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Revoking a group extension
|
||||
|
||||
You can revoke a group extension by using the `revokeExtend` method:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const parentGroup = Group.create();
|
||||
const childGroup = Group.create();
|
||||
|
||||
childGroup.extend(parentGroup);
|
||||
|
||||
// Revoke the extension
|
||||
await childGroup.revokeExtend(parentGroup);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Getting all parent groups
|
||||
|
||||
You can get all the parent groups of a group by calling the `getParentGroups` method:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const childGroup = Group.create();
|
||||
const parentGroup = Group.create();
|
||||
childGroup.extend(parentGroup);
|
||||
|
||||
console.log(childGroup.getParentGroups()); // [parentGroup]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Example: Team Hierarchy
|
||||
|
||||
Here's a practical example of using group inheritance for team permissions:
|
||||
|
||||
@@ -9,8 +9,6 @@ Every CoValue has an owner, which can be a `Group` or an `Account`.
|
||||
You can use a `Group` to grant access to a CoValue to multiple users. These users can
|
||||
have different roles, such as "writer", "reader" or "admin".
|
||||
|
||||
...more docs coming soon
|
||||
|
||||
## Creating a Group
|
||||
|
||||
Here's how you can create a `Group`.
|
||||
@@ -19,7 +17,7 @@ Here's how you can create a `Group`.
|
||||
```tsx
|
||||
import { Group } from "jazz-tools";
|
||||
|
||||
const group = Group.create({ owner: me });
|
||||
const group = Group.create();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -53,7 +51,6 @@ const bob = await Account.load(bobsID as ID<Account>, []);
|
||||
group.addMember(bob, "writer");
|
||||
```
|
||||
</CodeGroup>
|
||||
...more docs coming soon
|
||||
|
||||
## Getting the Group of an existing CoValue
|
||||
|
||||
@@ -77,7 +74,47 @@ import { Group } from "jazz-tools";
|
||||
|
||||
const group = existingCoValue._owner.castAs(Group);
|
||||
group.addMember(bob, "writer");
|
||||
group.myRole();
|
||||
|
||||
const role = group.getRoleOf(bob);
|
||||
```
|
||||
</CodeGroup>
|
||||
...more docs coming soon
|
||||
|
||||
## Checking the permissions
|
||||
|
||||
You can check the permissions of an account on a CoValue by using the `canRead`, `canWrite` and `canAdmin` methods.
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
const value = await MyCoMap.load(valueID, {});
|
||||
const me = Account.getMe();
|
||||
|
||||
if (me.canAdmin(value)) {
|
||||
console.log("I can share value with others");
|
||||
} else if (me.canWrite(value)) {
|
||||
console.log("I can edit value");
|
||||
} else if (me.canRead(value)) {
|
||||
console.log("I can view value");
|
||||
} else {
|
||||
console.log("I cannot access value");
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To check the permissions of another account, you need to load it first:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
const value = await MyCoMap.load(valueID, {});
|
||||
const bob = await Account.load(accountID, []);
|
||||
|
||||
if (bob.canAdmin(value)) {
|
||||
console.log("Bob can share value with others");
|
||||
} else if (bob.canWrite(value)) {
|
||||
console.log("Bob can edit value");
|
||||
} else if (bob.canRead(value)) {
|
||||
console.log("Bob can view value");
|
||||
} else {
|
||||
console.log("Bob cannot access value");
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -331,7 +331,7 @@ This works because CoValues
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const unsub = issue.subscribe([], (updatedIssue) => console.log(updatedIssue));
|
||||
const unsub = issue.subscribe({ resolve: true }, (updatedIssue) => console.log(updatedIssue));
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -339,7 +339,7 @@ This works because CoValues
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const unsub = Issue.subscribe(issueID, me, [], (updatedIssue) => {
|
||||
const unsub = Issue.subscribe(issueID, me, { resolve: true }, (updatedIssue) => {
|
||||
console.log(updatedIssue);
|
||||
});
|
||||
```
|
||||
@@ -358,7 +358,7 @@ This works because CoValues
|
||||
function useCoState<V extends CoValue>(Schema: CoValueClass<V>, id?: ID<V>): V | undefined {
|
||||
const [value, setValue] = useState<V>();
|
||||
|
||||
useEffect(() => Schema.subscribe(id, [], setValue), [id]);
|
||||
useEffect(() => Schema.subscribe(id, { resolve: true }, setValue), [id]);
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -454,7 +454,7 @@ function App() { // old
|
||||
const issue = useCoState(Issue, issueID); // old
|
||||
// old
|
||||
const createIssue = () => { // old
|
||||
const group = Group.create({ owner: me });
|
||||
const group = Group.create();
|
||||
group.addMember("everyone", "writer");
|
||||
// old
|
||||
const newIssue = Issue.create( // old
|
||||
@@ -605,6 +605,8 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {
|
||||
<button onClick={createAndAddIssue}>Create Issue</button>
|
||||
</div>
|
||||
</div>
|
||||
) : project === null ? (
|
||||
<div>Project not found or access denied</div>
|
||||
) : (
|
||||
<div>Loading project...</div>
|
||||
);
|
||||
@@ -635,7 +637,7 @@ import { IssueComponent } from "./Issue.tsx"; // old
|
||||
import { useCoState } from "jazz-react"; // old
|
||||
// old
|
||||
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {// old
|
||||
const project = useCoState(Project, projectID, { issues: [{}] });
|
||||
const project = useCoState(Project, projectID, { resolve: { issues: { $each: true } } });
|
||||
|
||||
const createAndAddIssue = () => {// old
|
||||
project?.issues.push(Issue.create({
|
||||
@@ -663,7 +665,7 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {//
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The loading-depth spec `{ issues: [{}] }` means "in `Project`, load `issues` and load each item in `issues` shallowly". (Since an `Issue` doesn't have any further references, "shallowly" actually means all its properties will be available).
|
||||
The loading-depth spec `{ resolve: { issues: { $each: true } } }` means "in `Project`, load `issues` and load each item in `issues` deeply". (Since an `Issue` doesn't have any further references, "deeply" actually means all its properties will be available).
|
||||
|
||||
- Now, we can get rid of a lot of conditional accesses because we know that once `project` is loaded, `project.issues` and each `Issue` in it will be loaded as well.
|
||||
- This also results in only one rerender and visual update when everything is loaded, which is faster (especially for long lists) and gives you more control over the loading UX.
|
||||
@@ -747,7 +749,9 @@ import { createInviteLink } from "jazz-react";
|
||||
// old
|
||||
|
||||
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {// old
|
||||
const project = useCoState(Project, projectID, { issues: [{}] }); // old
|
||||
const project = useCoState(Project, projectID, { resolve: { issues: { $each: true } } }); // old
|
||||
|
||||
const { me } = useAccount();
|
||||
|
||||
const invite = (role: "reader" | "writer") => {
|
||||
const link = createInviteLink(project, role, { valueHint: "project" });
|
||||
@@ -766,7 +770,7 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {//
|
||||
return project ? (// old
|
||||
<div>// old
|
||||
<h1>{project.name}</h1>// old
|
||||
{project._owner?.myRole() === "admin" && (
|
||||
{me.canAdmin(project) && (
|
||||
<>
|
||||
<button onClick={() => invite("reader")}>Invite Guest</button>
|
||||
<button onClick={() => invite("writer")}>Invite Member</button>
|
||||
|
||||
@@ -146,8 +146,9 @@ Jazz waits for the migration to finish before passing the account to your app's
|
||||
```ts
|
||||
export class MyAppAccount extends Account {
|
||||
root = co.ref(MyAppRoot);
|
||||
profile = co.ref(MyAppProfile);
|
||||
|
||||
async migrate() {
|
||||
async migrate(this: MyAppAccount, creationProps?: { name: string }) {
|
||||
// we specifically need to check for undefined,
|
||||
// because the root might simply be not loaded (`null`) yet
|
||||
if (this.root === undefined) {
|
||||
@@ -159,6 +160,17 @@ export class MyAppAccount extends Account {
|
||||
myContacts: ListOfAccounts.create([], Group.create())
|
||||
});
|
||||
}
|
||||
|
||||
if (this.profile === undefined) {
|
||||
const profileGroup = Group.create();
|
||||
// Unlike the root, we want the profile to be publicly readable.
|
||||
profileGroup.addMember("everyone", "reader");
|
||||
|
||||
this.profile = MyAppProfile.create({
|
||||
name: creationProps?.name,
|
||||
bookmarks: ListOfBookmarks.create([], profileGroup),
|
||||
}, profileGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -179,7 +191,7 @@ Now let's say we want to add a `myBookmarks` field to the `root` schema:
|
||||
export class MyAppAccount extends Account {
|
||||
root = co.ref(MyAppRoot);// old
|
||||
|
||||
async migrate() { // old
|
||||
async migrate(this: MyAppAccount) {
|
||||
if (this.root === undefined) { // old
|
||||
this.root = MyAppRoot.create({ // old
|
||||
myChats: ListOfChats.create([], Group.create()), // old
|
||||
@@ -188,12 +200,10 @@ export class MyAppAccount extends Account {
|
||||
} // old
|
||||
|
||||
// We need to load the root field to check for the myContacts field
|
||||
const result = await this.ensureLoaded({
|
||||
const { root } = await this.ensureLoaded({
|
||||
root: {},
|
||||
});
|
||||
|
||||
const { root } = result;
|
||||
|
||||
// we specifically need to check for undefined,
|
||||
// because myBookmarks might simply be not loaded (`null`) yet
|
||||
if (root.myBookmarks === undefined) {
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
import { Alert } from "gcmp-design-system/src/app/components/atoms/Alert";
|
||||
|
||||
export const metadata = { title: "Upgrade to Jazz 0.11.0" };
|
||||
|
||||
# Jazz 0.11.0 is out!
|
||||
|
||||
Jazz 0.11.0 brings several improvements to member handling, roles, and permissions management. This guide will help you upgrade your application to the latest version.
|
||||
|
||||
## What's new?
|
||||
Here is what's changed in this release:
|
||||
- [New permissions check APIs](#new-permissions-check-apis): New methods like `canRead`, `canWrite`, `canAdmin`, and `getRoleOf` to simplify permission checks.
|
||||
- [Group.revokeExtend](#grouprevokeextend): New method to revoke group extension permissions.
|
||||
- [Group.getParentGroups](#accountgetparentgroups): New method to get all the parent groups of an account.
|
||||
- [Account Profile & Migrations](#account-profile--migrations): Fixed issues with custom account profile migrations for a more consistent experience
|
||||
- [Dropped support for Accounts owning Profiles](#dropped-support-for-accounts-owning-profiles): Profiles can now only be owned by Groups.
|
||||
- [Group.members now includes inherited members](#member-inheritance-changes): Updated behavior for the `members` getter method to include inherited members and have a more intuitive type definition.
|
||||
|
||||
## New Features
|
||||
|
||||
### New permissions check APIs
|
||||
|
||||
New methods have been added to both `Account` and `Group` classes to improve permission handling:
|
||||
|
||||
#### Permission checks
|
||||
|
||||
The new `canRead`, `canWrite` and `canAdmin` methods on `Account` allow you to easily check if the account has specific permissions on a CoValue:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```typescript
|
||||
const me = Account.getMe();
|
||||
|
||||
if (me.canAdmin(value)) {
|
||||
console.log("I can share value with others");
|
||||
} else if (me.canWrite(value)) {
|
||||
console.log("I can edit value");
|
||||
} else if (me.canRead(value)) {
|
||||
console.log("I can view value");
|
||||
} else {
|
||||
console.log("I cannot access value");
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Getting the role of an account
|
||||
|
||||
The `getRoleOf` method has been added to query the role of specific entities:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const group = Group.create();
|
||||
group.getRoleOf(me); // admin
|
||||
group.getRoleOf(Eve); // undefined
|
||||
|
||||
group.addMember(Eve, "writer");
|
||||
group.getRoleOf(Eve); // writer
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Group.revokeExtend
|
||||
|
||||
We added a new method to revoke the extend of a Group:
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
function addTrackToPlaylist(playlist: Playlist, track: MusicTrack) {
|
||||
const trackGroup = track._owner.castAs(Group);
|
||||
trackGroup.extend(playlist._owner, "reader"); // Grant read access to the track to the playlist accounts
|
||||
|
||||
playlist.tracks.push(track);
|
||||
}
|
||||
|
||||
function removeTrackFromPlaylist(playlist: Playlist, track: MusicTrack) {
|
||||
const trackGroup = track._owner.castAs(Group);
|
||||
trackGroup.revokeExtend(playlist._owner); // Revoke access to the track to the playlist accounts
|
||||
|
||||
const index = playlist.tracks.findIndex(t => t.id === track.id);
|
||||
if (index !== -1) {
|
||||
playlist.tracks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Group.getParentGroups
|
||||
|
||||
The `getParentGroups` method has been added to `Group` to get all the parent groups of a group.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const childGroup = Group.create();
|
||||
const parentGroup = Group.create();
|
||||
childGroup.extend(parentGroup);
|
||||
|
||||
console.log(childGroup.getParentGroups()); // [parentGroup]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Account Profile & Migrations
|
||||
|
||||
The previous way of making the `Profile` migration work was to assume that the profile was always already there:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
export class MyAppAccount extends Account {
|
||||
profile = co.ref(MyAppProfile);
|
||||
|
||||
async migrate(this: MyAppAccount, creationProps: { name: string, lastName: string }) {
|
||||
if (creationProps) {
|
||||
const { profile } = await this.ensureLoaded({ profile: {} });
|
||||
|
||||
profile.name = creationProps.name;
|
||||
profile.bookmarks = ListOfBookmarks.create([], profileGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
This was kind-of tricky to picture, and having different migration strategies for different CoValues was confusing.
|
||||
|
||||
We changed the logic so the default profile is created only if you didn't provide one in your migration.
|
||||
|
||||
This way you can use the same pattern for both `root` and `profile` migrations:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
export class MyAppAccount extends Account {
|
||||
profile = co.ref(MyAppProfile);
|
||||
|
||||
async migrate(this: MyAppAccount, creationProps?: { name: string }) {
|
||||
if (this.profile === undefined) {
|
||||
const profileGroup = Group.create();
|
||||
profileGroup.addMember("everyone", "reader");
|
||||
|
||||
this.profile = MyAppProfile.create({
|
||||
name: creationProps?.name,
|
||||
bookmarks: ListOfBookmarks.create([], profileGroup),
|
||||
}, profileGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Alert variant="warning" title="Warning" className="mt-4">
|
||||
If you provide a custom `Profile` in your `Account` schema and migration for a Worker account,
|
||||
make sure to also add `everyone` as member with `reader` role to the owning group.
|
||||
Failing to do so will prevent any account from sending messages to the Worker's Inbox.
|
||||
</Alert>
|
||||
|
||||
### Dropped support for Accounts owning Profiles
|
||||
Starting from `0.11.0` `Profile`s can only be owned by `Group`s.
|
||||
|
||||
<Alert variant="info" title="Note" className="mt-4">
|
||||
Existing profiles owned by `Account`s will still work, but you will get incorrect types when accessing a `Profile`'s `_owner`.
|
||||
</Alert>
|
||||
|
||||
### Member Inheritance Changes
|
||||
|
||||
The behavior of groups' `members` getter method has been updated to return both direct members and inherited ones from ancestor groups.
|
||||
This might affect your application if you were relying on only direct members being returned.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
/**
|
||||
* The following pseudocode only illustrates the inheritance logic,
|
||||
* the actual implementation is different.
|
||||
*/
|
||||
|
||||
const parentGroup = Group.create();
|
||||
parentGroup.addMember(John, "admin");
|
||||
|
||||
const childGroup = Group.create();
|
||||
childGroup.addMember(Eve, "admin");
|
||||
|
||||
childGroup.extend(parentGroup);
|
||||
|
||||
console.log(childGroup.members);
|
||||
// Before 0.11.0
|
||||
// [Eve]
|
||||
|
||||
// After 0.11.0
|
||||
// [Eve, John]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Additionally:
|
||||
- now `Group.members` doesn't include the `everyone` member anymore
|
||||
- the account type in `Group.members` is now the globally registered Account schema and we have removed the `co.members` way to define an AccountSchema for members
|
||||
|
||||
If you need to explicitly check if "everyone" is a member of a group, you can use the `getRoleOf` method instead:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
if (group.getRoleOf("everyone")) {
|
||||
console.log("Everyone has access to the group");
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
#### Migration Steps
|
||||
|
||||
1. Review your member querying logic to account for inherited members.
|
||||
2. Update your permission checking code to utilize the new [`hasPermissions` and `getRoleOf`](#enhanced-permission-management) methods.
|
||||
3. Consider implementing `"everyone"` role checks where appropriate.
|
||||
|
||||
### Removed auto-update of `profile.name` in `usePasskeyAuth`
|
||||
|
||||
The `usePasskeyAuth` hook now doesn't update the `profile.name` if the provided username is empty.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
> I'm getting the following error: `Error: Profile must be owned by a Group`
|
||||
|
||||
If you previously forced a migration of your `Account` schema to include a custom `Profile`,
|
||||
and assigned its ownership to an `Account`, you need to recreate your profile code and assign it to a `Group` instead.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
export class MyAppAccount extends Account {
|
||||
profile = co.ref(MyAppProfile);
|
||||
|
||||
override async migrate() {
|
||||
// ...
|
||||
|
||||
const me = await this.ensureLoaded({
|
||||
profile: {},
|
||||
});
|
||||
|
||||
if ((me.profile._owner as Group | Account)._type === "Account") {
|
||||
const profileGroup = Group.create();
|
||||
profileGroup.addMember("everyone", "reader");
|
||||
|
||||
// recreate your profile here...
|
||||
me.profile = Profile.create(
|
||||
{
|
||||
name: me.profile.name,
|
||||
},
|
||||
profileGroup,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -0,0 +1,110 @@
|
||||
import { CodeGroup } from '@/components/forMdx'
|
||||
|
||||
export const metadata = { title: "Jazz 0.11.0 - Deeply resolved data" };
|
||||
|
||||
# Jazz 0.11.0 - Deeply resolved data
|
||||
|
||||
<h2 className="not-prose text-sm text-stone-600 dark:text-stone-400 mb-5 pb-2 border-b">
|
||||
15 March 2025
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
Jazz 0.11.0 makes it easier and safer to load nested data. You can now specify exactly which nested data you want to load, and Jazz will check permissions and handle missing data gracefully. This helps catch errors earlier during development and makes your code more reliable.
|
||||
|
||||
<h3>What's new?</h3>
|
||||
- New resolve API for a more type-safe deep loading
|
||||
- Improved permission checks on deep loading
|
||||
- Easier type safety with the Resolved type
|
||||
</div>
|
||||
|
||||
<h3>New Resolve API</h3>
|
||||
<div>
|
||||
We're introducing a new resolve API for deep loading, more friendly to TypeScript, IDE autocompletion and LLMs.
|
||||
|
||||
To get started with the new resolve API, replace empty array/object parameters with structured resolve configs:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
// Before
|
||||
const playlist = useCoState(Playlist, id, [{}]);
|
||||
const { me } = useAccount({ root: { playlists: [] } });
|
||||
|
||||
// After
|
||||
const playlist = useCoState(Playlist, id, {
|
||||
resolve: { $each: true }
|
||||
});
|
||||
const { me } = useAccount({
|
||||
resolve: { root: { playlists: true } }
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The new API works across all loading methods:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
// Before
|
||||
Playlist.load(id, account, {
|
||||
tracks: [],
|
||||
});
|
||||
|
||||
// After
|
||||
Playlist.load(id, {
|
||||
loadAs: account,
|
||||
resolve: { tracks: true }
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
</div>
|
||||
|
||||
<h3>Improved permission checks on deep loading</h3>
|
||||
<div>
|
||||
Now `useCoState` will return `null` when the current user lacks permissions to load the requested data.
|
||||
|
||||
Previously, `useCoState` would return `undefined` if the current user lacked permissions, making it hard to tell if the value is loading or if it's missing.
|
||||
|
||||
Now `undefined` means that the value is definitely loading, and `null` means that the value is temporarily missing.
|
||||
|
||||
We also have implemented a more granular permission checking, where if an optional coValue cannot be accessed, useCoState will return the data stripped of that coValue.
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
class ListOfTracks extends CoList.Of(co.optional.ref(Track)) {}
|
||||
// Before (ambiguous states)
|
||||
const value = useCoState(ListOfTracks, id, [{}]);
|
||||
if (value === undefined) return <div>Loading or access denied</div>;
|
||||
if (value === null) return <div>Not found</div>;
|
||||
|
||||
// After
|
||||
const value = useCoState(ListOfTracks, id, { resolve: { $each: true } });
|
||||
if (value === undefined) return <div>Loading...</div>;
|
||||
if (value === null) return <div>Not found or access denied</div>;
|
||||
|
||||
// If the current user lacks permissions to load a Track, the ListOfTracks will be returned without that Track.
|
||||
return tracks.map(track => track && <MusicTrack track={track} />);
|
||||
```
|
||||
</CodeGroup>
|
||||
</div>
|
||||
|
||||
<h3>Type Safety Improvements</h3>
|
||||
<div>
|
||||
The new `Resolved` type can be used to define what kind of deeply loaded data you expect in your parameters:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
// Before
|
||||
type PlaylistResolved = Resolved<Playlist, {
|
||||
tracks: { $each: true }
|
||||
}>;
|
||||
|
||||
// After
|
||||
function TrackList({ playlist }: { playlist: PlaylistResolved }) {
|
||||
// Safe access to resolved tracks
|
||||
return playlist.tracks.map(track => /* ... */);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</div>
|
||||
@@ -18,20 +18,6 @@ import {
|
||||
PropDecl,
|
||||
} from "./tags";
|
||||
|
||||
function isDeclarationReflection(child: any): child is DeclarationReflection {
|
||||
return (
|
||||
'kind' in child &&
|
||||
'name' in child &&
|
||||
'flags' in child &&
|
||||
(child.kind === ReflectionKind.Class ||
|
||||
child.kind === ReflectionKind.Interface ||
|
||||
child.kind === ReflectionKind.TypeAlias ||
|
||||
child.kind === ReflectionKind.Function ||
|
||||
child.kind === ReflectionKind.Property ||
|
||||
child.kind === ReflectionKind.Method)
|
||||
);
|
||||
}
|
||||
|
||||
export async function PackageDocs({
|
||||
package: packageName,
|
||||
}: {
|
||||
@@ -50,7 +36,7 @@ export async function PackageDocs({
|
||||
return (
|
||||
<section key={category.title}>
|
||||
<h2>{category.title}</h2>
|
||||
{category.children.filter(isDeclarationReflection).map((child) => (
|
||||
{category.children.map((child) => (
|
||||
<RenderPackageChild
|
||||
child={child}
|
||||
key={child.id}
|
||||
@@ -203,7 +189,7 @@ function RenderClassOrInterface({
|
||||
),
|
||||
)}
|
||||
/>
|
||||
{category.children.filter(isDeclarationReflection).map((prop) => (
|
||||
{category.children.map((prop) => (
|
||||
<RenderProp prop={prop} klass={classOrInterface} key={prop.id} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Deserializer, FileRegistry, JSONOutput, ProjectReflection } from "typedoc";
|
||||
import { Deserializer, JSONOutput, ProjectReflection } from "typedoc";
|
||||
|
||||
import JazzBrowserMediaImagesDocs from "../../typedoc/jazz-browser-media-images.json";
|
||||
import JazzBrowserDocs from "../../typedoc/jazz-browser.json";
|
||||
@@ -7,19 +7,17 @@ import JazzReactDocs from "../../typedoc/jazz-react.json";
|
||||
import JazzToolsDocs from "../../typedoc/jazz-tools.json";
|
||||
|
||||
const docs = {
|
||||
"jazz-tools": JazzToolsDocs,
|
||||
"jazz-react": JazzReactDocs,
|
||||
"jazz-browser": JazzBrowserDocs,
|
||||
"jazz-browser-media-images": JazzBrowserMediaImagesDocs,
|
||||
"jazz-nodejs": JazzNodejsDocs,
|
||||
} as const;
|
||||
"jazz-tools": JazzToolsDocs as JSONOutput.ProjectReflection,
|
||||
"jazz-react": JazzReactDocs as JSONOutput.ProjectReflection,
|
||||
"jazz-browser": JazzBrowserDocs as JSONOutput.ProjectReflection,
|
||||
"jazz-browser-media-images":
|
||||
JazzBrowserMediaImagesDocs as JSONOutput.ProjectReflection,
|
||||
"jazz-nodejs": JazzNodejsDocs as JSONOutput.ProjectReflection,
|
||||
};
|
||||
|
||||
export async function requestProject(
|
||||
packageName: keyof typeof docs,
|
||||
): Promise<ProjectReflection> {
|
||||
const deserializer = new Deserializer({} as any);
|
||||
return deserializer.reviveProject(packageName, docs[packageName] as unknown as JSONOutput.ProjectReflection, {
|
||||
projectRoot: ".",
|
||||
registry: new FileRegistry(),
|
||||
});
|
||||
return deserializer.reviveProject(docs[packageName], packageName);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ export function JazzFooter() {
|
||||
{
|
||||
title: "About",
|
||||
links: [
|
||||
{
|
||||
href: "/status",
|
||||
label: "Status",
|
||||
},
|
||||
{
|
||||
href: "https://garden.co/team",
|
||||
label: "Team",
|
||||
@@ -23,11 +27,6 @@ export function JazzFooter() {
|
||||
label: "Blog",
|
||||
newTab: true,
|
||||
},
|
||||
{
|
||||
href: "https://github.com/garden-co/jazz/releases",
|
||||
label: "Releases",
|
||||
newTab: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -38,13 +38,8 @@ export function JazzNav({ sections }: { sections?: NavSection[] }) {
|
||||
newTab: true,
|
||||
},
|
||||
{
|
||||
title: "Releases",
|
||||
href: "https://github.com/garden-co/jazz/releases",
|
||||
newTab: true,
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
href: "/status",
|
||||
title: "Status",
|
||||
},
|
||||
]}
|
||||
socials={socials}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user