You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
1
+
# 🎨 [instldraw](https://draw.instantdb.com/): Multiplayer Drawings with Instant 🤝 tldraw
2
+
3
+
Welcome! We took [tldraw](https://tldraw.dev/)'s infinite canvas and added real-time team collaboration powered by Instant's [graph database](https://www.instantdb.com/docs/instaml) and [presence](https://www.instantdb.com/docs/presence-and-topics). If you’re curious, check out [this essay](https://www.instantdb.com/essays/next_firebase) to learn more about “why Instant”.
- Auth via ["magic code" login](https://www.instantdb.com/docs/auth#magic-codes).
10
+
- A full-fledged teams/memberships/invites [data model](https://www.instantdb.com/docs/modeling-data) and secured by [permissions](https://www.instantdb.com/docs/permissions).
11
+
- Multiplayer cursors via [presence](https://www.instantdb.com/docs/presence-and-topics).
12
+
13
+
## 🛣️ Getting Started
2
14
3
-
##Getting Started
15
+
### ⚡ 1. Create a free account on [Instant](https://www.instantdb.com/)
4
16
5
-
First, run the development server:
17
+
Head on over to the [Instant dashboard](https://www.instantdb.com/dash), grab your app's ID, and plop it into a `.env.development.local` file:
18
+
19
+
```bash
20
+
NEXT_PUBLIC_INSTANT_APP_ID=__YOUR_APP_ID__
21
+
```
22
+
23
+
### 🔥 2. Install and Run
24
+
25
+
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
26
+
27
+
Install dependencies with your package manager of choice, then start the development server:
6
28
7
29
```bash
8
30
npm run dev
@@ -14,27 +36,125 @@ pnpm dev
14
36
bun dev
15
37
```
16
38
17
-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
39
+
That's it! 🎉 Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. Next.js will live-update the page as you edit the app's code.
40
+
41
+
Finally, to add a layer of security to your app, copy `resources/instant-perms.json` into the [Instant Permissions editor](https://www.instantdb.com/dash?s=main&t=perms).
42
+
43
+
## 📚 Reference
44
+
45
+
### 📂 Structure
18
46
19
-
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
47
+
The app is broken up into two pages:
20
48
21
-
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
49
+
- An index page `/`, which serves as a dashboard and directory of teams and drawings.
50
+
- A drawing page `/drawings/:id` where we render the [tldraw](https://tldraw.dev/) canvas.
22
51
23
-
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
52
+
Both pages load data from Instant with `db.useQuery` and write data using functions from `src/mutators.ts`.
24
53
25
-
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
54
+
### 📄 Notable Files
26
55
27
-
## Learn More
56
+
-`src/pages/index.tsx`: The main dashboard: list and manage teams, teammates, and drawings.
57
+
-`src/pages/drawings/[id].tsx`: The canvas! Uses `useInstantStore` and `useInstantPresence` to add multiplayer.
58
+
-`src/lib/useInstantStore.tsx`: A collaborative backend for tldraw built on top of Instant's real-time database. Uses InstaML's [merge()](https://www.instantdb.com/docs/instaml#merge) for fine-grained updates to the drawing state.
59
+
-`src/lib/useInstantPresence.tsx`: A React hook responsible for keeping tldraw's editor state in sync with [Instant's real-time presence API](https://www.instantdb.com/docs/presence-and-topics).
60
+
-`src/mutators.ts`: All functions that update Instant's database live here. You can inspect and edit your database using the [Instant Explorer](https://www.instantdb.com/dash?s=main&t=explorer).
28
61
29
-
To learn more about Next.js, take a look at the following resources:
62
+
### 🧩 Data Model
30
63
31
-
-[Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
32
-
-[Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
64
+
```graphql
65
+
drawings {
66
+
team: @has_one(teams)
67
+
state: DrawingState{} # see src/types.ts
68
+
name: string
69
+
}
33
70
34
-
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
71
+
teams {
72
+
drawings: @has_many(drawings)
73
+
invites: @has_many(invites)
74
+
memberships: @has_many(memberships)
75
+
name: string
76
+
creatorId: string
77
+
}
35
78
36
-
## Deploy on Vercel
79
+
invites {
80
+
team: @has_one(teams)
81
+
teamId: string
82
+
teamName: string
83
+
userEmail: string
84
+
}
37
85
38
-
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
86
+
memberships {
87
+
team: @has_one(teams)
88
+
teamId: string
89
+
userId: string
90
+
userEmail: string
91
+
}
92
+
```
93
+
94
+
### 🛡️ Permissions
95
+
96
+
Instant provides a [permissions layer](https://www.instantdb.com/docs/permissions) to define access control for all entities in your database using [CEL](https://github.com/google/cel-spec/blob/master/doc/langdef.md). Copy the contents of [`resources/instant-perms.json`](https://github.com/jsventures/instldraw/blob/main/resources/instant-perms.json) below into your app's [permissions editor](https://www.instantdb.com/dash?s=main&t=perms).
97
+
98
+
```json
99
+
{
100
+
"teams": {
101
+
"bind": [
102
+
"isCreator",
103
+
"auth.id == data.creatorId",
104
+
"isMember",
105
+
"auth.id in data.ref('memberships.userId')"
106
+
],
107
+
"allow": {
108
+
"view": "isMember",
109
+
"create": "isCreator",
110
+
"delete": "isCreator",
111
+
"update": "isCreator"
112
+
}
113
+
},
114
+
"invites": {
115
+
"bind": [
116
+
"isMember",
117
+
"auth.id in data.ref('team.memberships.userId')",
118
+
"isInvitee",
119
+
"auth.email == data.userEmail"
120
+
],
121
+
"allow": {
122
+
"view": "isInvitee",
123
+
"create": "isMember",
124
+
"delete": "isMember",
125
+
"update": "false"
126
+
}
127
+
},
128
+
"drawings": {
129
+
"bind": ["isMember", "auth.id in data.ref('team.memberships.userId')"],
130
+
"allow": {
131
+
"view": "isMember",
132
+
"create": "isMember",
133
+
"delete": "isMember",
134
+
"update": "isMember"
135
+
}
136
+
},
137
+
"memberships": {
138
+
"bind": [
139
+
"isMember",
140
+
"auth.id in data.ref('team.memberships.userId')",
141
+
"isInviteeOrCreator",
142
+
"size(data.ref('team.invites.id')) == 0 ? auth.id in data.ref('team.creatorId') : auth.email in data.ref('team.invites.userEmail')",
143
+
"isUser",
144
+
"auth.id == data.userId"
145
+
],
146
+
"allow": {
147
+
"view": "isMember",
148
+
"create": "isInviteeOrCreator",
149
+
"delete": "isUser",
150
+
"update": "false"
151
+
}
152
+
}
153
+
}
154
+
```
39
155
40
-
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
156
+
-`data` is a variable provided by Instant that references the object being operated upon.
157
+
-`auth` is a variable provided by Instant that you can use to access the current user's ID and email.
158
+
- You can leverage Instant's graph database in your rule definitions using `data.ref(<key_path>)`! `ref` will traverse an entity's links and collect the values of your specified property path.
159
+
- For example, `teams.bind:isMember` above uses `data.ref('memberships.userId')` to collect all linked memberships for a given team, then select the `userId` property for each membership.
160
+
- You can use the `bind` param to define abstractions (`isMember`, `isInvitee`) that can be re-used across rules.
0 commit comments