automerge beginnings
This commit is contained in:
parent
5eb5789c23
commit
de7de98ee8
|
|
@ -10,6 +10,9 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.33.1",
|
"@anthropic-ai/sdk": "^0.33.1",
|
||||||
|
"@automerge/automerge": "^3.1.1",
|
||||||
|
"@automerge/automerge-repo": "^2.2.0",
|
||||||
|
"@automerge/automerge-repo-react-hooks": "^2.2.0",
|
||||||
"@daily-co/daily-js": "^0.60.0",
|
"@daily-co/daily-js": "^0.60.0",
|
||||||
"@daily-co/daily-react": "^0.20.0",
|
"@daily-co/daily-react": "^0.20.0",
|
||||||
"@tldraw/assets": "^3.6.0",
|
"@tldraw/assets": "^3.6.0",
|
||||||
|
|
@ -22,6 +25,7 @@
|
||||||
"@uiw/react-md-editor": "^4.0.5",
|
"@uiw/react-md-editor": "^4.0.5",
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"ai": "^4.1.0",
|
"ai": "^4.1.0",
|
||||||
|
"automerge": "^0.14.2",
|
||||||
"cherry-markdown": "^0.8.57",
|
"cherry-markdown": "^0.8.57",
|
||||||
"cloudflare-workers-unfurl": "^0.0.7",
|
"cloudflare-workers-unfurl": "^0.0.7",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
|
@ -182,6 +186,56 @@
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@automerge/automerge": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@automerge/automerge/-/automerge-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-x7tZiMBLk4/SKYimVEVl1/wPntT9buGvLOWCey9ZcH8JUsB0dgm49C0S7Ojzgvflcs2hc/YjiXRPcFeFkinIgw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@automerge/automerge-repo": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@automerge/automerge-repo/-/automerge-repo-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-/4cAxnUDPykqdSMJJiJvPlR2VY0JHJnvEoM7fO8qVXwQors34S0VkJEScXRJZExcVhWGhFTwCTWvUbb6hhWQIQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@automerge/automerge": "2.2.8 - 3",
|
||||||
|
"bs58check": "^3.0.1",
|
||||||
|
"cbor-x": "^1.3.0",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"fast-sha256": "^1.3.0",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
|
"xstate": "^5.9.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@automerge/automerge-repo-react-hooks": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@automerge/automerge-repo-react-hooks/-/automerge-repo-react-hooks-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-zHP44jXSCmV1DyyKdTKgv8nz7yrdCT7VFXs/QrF3YRhE/3czhhvbXo2zahGvOoM+jryH0eJxHTrJ7jMEtt/Ung==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@automerge/automerge": "2.2.8 - 3",
|
||||||
|
"@automerge/automerge-repo": "2.2.0",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"react-usestateref": "^1.0.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@automerge/automerge-repo-react-hooks/node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@automerge/automerge-repo/node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.26.2",
|
"version": "7.26.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||||
|
|
@ -473,6 +527,84 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-darwin-arm64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-darwin-x64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-linux-arm": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-linux-arm64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-linux-x64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@cbor-extract/cbor-extract-win32-x64": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@cloudflare/intl-types": {
|
"node_modules/@cloudflare/intl-types": {
|
||||||
"version": "1.5.6",
|
"version": "1.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/@cloudflare/intl-types/-/intl-types-1.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/@cloudflare/intl-types/-/intl-types-1.5.6.tgz",
|
||||||
|
|
@ -1838,6 +1970,18 @@
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
|
@ -4384,6 +4528,28 @@
|
||||||
"node": ">= 4.5.0"
|
"node": ">= 4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/automerge": {
|
||||||
|
"version": "0.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/automerge/-/automerge-0.14.2.tgz",
|
||||||
|
"integrity": "sha512-shiwuJHCbNRI23WZyIECLV4Ovf3WiAFJ7P9BH4l5gON1In/UUbjcSJKRygtIirObw2UQumeYxp3F2XBdSvQHnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immutable": "^3.8.2",
|
||||||
|
"transit-immutable-js": "^0.7.0",
|
||||||
|
"transit-js": "^0.8.861",
|
||||||
|
"uuid": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/automerge/node_modules/uuid": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||||
|
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bail": {
|
"node_modules/bail": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||||
|
|
@ -4400,6 +4566,12 @@
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/base-x": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/base64-arraybuffer": {
|
"node_modules/base64-arraybuffer": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||||
|
|
@ -4508,6 +4680,25 @@
|
||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bs58": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base-x": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bs58check": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "^1.2.0",
|
||||||
|
"bs58": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/btoa": {
|
"node_modules/btoa": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
|
||||||
|
|
@ -4608,6 +4799,37 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cbor-extract": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-gyp-build-optional-packages": "5.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"download-cbor-prebuilds": "bin/download-prebuilds.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@cbor-extract/cbor-extract-darwin-arm64": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-darwin-x64": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-linux-arm": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-linux-arm64": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-linux-x64": "2.2.0",
|
||||||
|
"@cbor-extract/cbor-extract-win32-x64": "2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cbor-x": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"cbor-extract": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ccount": {
|
"node_modules/ccount": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
|
||||||
|
|
@ -6530,6 +6752,12 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-sha256": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==",
|
||||||
|
"license": "Unlicense"
|
||||||
|
},
|
||||||
"node_modules/fastq": {
|
"node_modules/fastq": {
|
||||||
"version": "1.18.0",
|
"version": "1.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
|
||||||
|
|
@ -7390,6 +7618,15 @@
|
||||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/immutable": {
|
||||||
|
"version": "3.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
|
||||||
|
"integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/inflight": {
|
"node_modules/inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
|
@ -9155,6 +9392,21 @@
|
||||||
"node-gyp-build-test": "build-test.js"
|
"node-gyp-build-test": "build-test.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-gyp-build-optional-packages": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-gyp-build-optional-packages": "bin.js",
|
||||||
|
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||||
|
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.19",
|
"version": "2.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||||
|
|
@ -9866,6 +10118,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-usestateref": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-usestateref/-/react-usestateref-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-t8KLsI7oje0HzfzGhxFXzuwbf1z9vhBM1ptHLUIHhYqZDKFuI5tzdhEVxSNzUkYxwF8XdpOErzHlKxvP7sTERw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
|
@ -11097,6 +11358,25 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/transit-immutable-js": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/transit-immutable-js/-/transit-immutable-js-0.7.0.tgz",
|
||||||
|
"integrity": "sha512-Qga6EDAmVVoDwzPZ2fBvi3aj5E0V6NLy/spCFvspVd4BfY2/3Jr5yyynbSTC2SAEipkJ4MHVH75xNcTOlz9UaQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"immutable": ">= 3",
|
||||||
|
"transit-js": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/transit-js": {
|
||||||
|
"version": "0.8.874",
|
||||||
|
"resolved": "https://registry.npmjs.org/transit-js/-/transit-js-0.8.874.tgz",
|
||||||
|
"integrity": "sha512-IDJJGKRzUbJHmN0P15HBBa05nbKor3r2MmG6aSt0UxXIlJZZKcddTk67/U7WyAeW9Hv/VYI02IqLzolsC4sbPA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tree-kill": {
|
"node_modules/tree-kill": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||||
|
|
@ -11526,7 +11806,6 @@
|
||||||
"https://github.com/sponsors/ctavan"
|
"https://github.com/sponsors/ctavan"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
|
|
@ -12457,6 +12736,16 @@
|
||||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/xstate": {
|
||||||
|
"version": "5.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xstate/-/xstate-5.20.1.tgz",
|
||||||
|
"integrity": "sha512-i9ZpNnm/XhCOMUxae1suT8PjYNTStZWbhmuKt4xeTPaYG5TS0Fz0i+Ka5yxoNPpaHW3VW6JIowrwFgSTZONxig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/xstate"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.33.1",
|
"@anthropic-ai/sdk": "^0.33.1",
|
||||||
|
"@automerge/automerge": "^3.1.1",
|
||||||
|
"@automerge/automerge-repo": "^2.2.0",
|
||||||
|
"@automerge/automerge-repo-react-hooks": "^2.2.0",
|
||||||
"@daily-co/daily-js": "^0.60.0",
|
"@daily-co/daily-js": "^0.60.0",
|
||||||
"@daily-co/daily-react": "^0.20.0",
|
"@daily-co/daily-react": "^0.20.0",
|
||||||
"@tldraw/assets": "^3.6.0",
|
"@tldraw/assets": "^3.6.0",
|
||||||
|
|
@ -29,6 +32,7 @@
|
||||||
"@uiw/react-md-editor": "^4.0.5",
|
"@uiw/react-md-editor": "^4.0.5",
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"ai": "^4.1.0",
|
"ai": "^4.1.0",
|
||||||
|
"automerge": "^0.14.2",
|
||||||
"cherry-markdown": "^0.8.57",
|
"cherry-markdown": "^0.8.57",
|
||||||
"cloudflare-workers-unfurl": "^0.0.7",
|
"cloudflare-workers-unfurl": "^0.0.7",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { TLRecord, RecordId, TLStore } from "@tldraw/tldraw"
|
||||||
|
import { Patch } from "@automerge/automerge"
|
||||||
|
|
||||||
|
export function applyAutomergePatchesToTLStore(
|
||||||
|
patches: Patch[],
|
||||||
|
store: TLStore
|
||||||
|
) {
|
||||||
|
const toRemove: TLRecord["id"][] = []
|
||||||
|
const updatedObjects: { [id: string]: TLRecord } = {}
|
||||||
|
|
||||||
|
patches.forEach((patch) => {
|
||||||
|
if (!isStorePatch(patch)) return
|
||||||
|
|
||||||
|
const id = pathToId(patch.path)
|
||||||
|
const record =
|
||||||
|
updatedObjects[id] || JSON.parse(JSON.stringify(store.get(id) || {}))
|
||||||
|
|
||||||
|
switch (patch.action) {
|
||||||
|
case "insert": {
|
||||||
|
updatedObjects[id] = applyInsertToObject(patch, record)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "put":
|
||||||
|
updatedObjects[id] = applyPutToObject(patch, record)
|
||||||
|
break
|
||||||
|
case "splice": {
|
||||||
|
updatedObjects[id] = applySpliceToObject(patch, record)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "del": {
|
||||||
|
const id = pathToId(patch.path)
|
||||||
|
toRemove.push(id as TLRecord["id"])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.log("Unsupported patch:", patch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const toPut = Object.values(updatedObjects)
|
||||||
|
|
||||||
|
// put / remove the records in the store
|
||||||
|
console.log({ patches, toPut })
|
||||||
|
store.mergeRemoteChanges(() => {
|
||||||
|
if (toRemove.length) store.remove(toRemove)
|
||||||
|
if (toPut.length) store.put(toPut)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isStorePatch = (patch: Patch): boolean => {
|
||||||
|
return patch.path[0] === "store" && patch.path.length > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// path: ["store", "camera:page:page", "x"] => "camera:page:page"
|
||||||
|
const pathToId = (path: (string | number)[]): RecordId<any> => {
|
||||||
|
return path[1] as RecordId<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyInsertToObject = (patch: any, object: any): TLRecord => {
|
||||||
|
const { path, values } = patch
|
||||||
|
let current = object
|
||||||
|
const insertionPoint = path[path.length - 1]
|
||||||
|
const pathEnd = path[path.length - 2]
|
||||||
|
const parts = path.slice(2, -2)
|
||||||
|
for (const part of parts) {
|
||||||
|
if (current[part] === undefined) {
|
||||||
|
throw new Error("NO WAY")
|
||||||
|
}
|
||||||
|
current = current[part]
|
||||||
|
}
|
||||||
|
// splice is a mutator... yay.
|
||||||
|
const clone = current[pathEnd].slice(0)
|
||||||
|
clone.splice(insertionPoint, 0, ...values)
|
||||||
|
current[pathEnd] = clone
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyPutToObject = (patch: any, object: any): TLRecord => {
|
||||||
|
const { path, value } = patch
|
||||||
|
let current = object
|
||||||
|
// special case
|
||||||
|
if (path.length === 2) {
|
||||||
|
// this would be creating the object, but we have done
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = path.slice(2, -2)
|
||||||
|
const property = path[path.length - 1]
|
||||||
|
const target = path[path.length - 2]
|
||||||
|
|
||||||
|
if (path.length === 3) {
|
||||||
|
return { ...object, [property]: value }
|
||||||
|
}
|
||||||
|
|
||||||
|
// default case
|
||||||
|
for (const part of parts) {
|
||||||
|
current = current[part]
|
||||||
|
}
|
||||||
|
current[target] = { ...current[target], [property]: value }
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
const applySpliceToObject = (patch: any, object: any): TLRecord => {
|
||||||
|
const { path, value } = patch
|
||||||
|
let current = object
|
||||||
|
const insertionPoint = path[path.length - 1]
|
||||||
|
const pathEnd = path[path.length - 2]
|
||||||
|
const parts = path.slice(2, -2)
|
||||||
|
for (const part of parts) {
|
||||||
|
if (current[part] === undefined) {
|
||||||
|
throw new Error("NO WAY")
|
||||||
|
}
|
||||||
|
current = current[part]
|
||||||
|
}
|
||||||
|
// TODO: we're not supporting actual splices yet because TLDraw won't generate them natively
|
||||||
|
if (insertionPoint !== 0) {
|
||||||
|
throw new Error("Splices are not supported yet")
|
||||||
|
}
|
||||||
|
current[pathEnd] = value // .splice(insertionPoint, 0, value)
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { type DocHandle } from "@automerge/automerge-repo"
|
||||||
|
import { type TLStoreSnapshot, Tldraw, track, useEditor } from "@tldraw/tldraw"
|
||||||
|
import "@tldraw/tldraw/tldraw.css"
|
||||||
|
|
||||||
|
import { useAutomergeStore } from "./useAutomergeStore"
|
||||||
|
|
||||||
|
interface TLDrawAutomergeExampleProps {
|
||||||
|
handle: DocHandle<TLStoreSnapshot>
|
||||||
|
userId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TLDrawAutomergeExample({
|
||||||
|
handle,
|
||||||
|
userId,
|
||||||
|
}: TLDrawAutomergeExampleProps) {
|
||||||
|
const store = useAutomergeStore({ handle, userId })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tldraw__editor">
|
||||||
|
<Tldraw autoFocus store={store} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const NameEditor = track(() => {
|
||||||
|
const editor = useEditor()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ pointerEvents: "all", display: "flex" }}>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
value={editor.user.getUserPreferences().color}
|
||||||
|
onChange={(e) => {
|
||||||
|
editor.user.updateUserPreferences({
|
||||||
|
color: e.currentTarget.value,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
value={editor.user.getUserPreferences().name}
|
||||||
|
onChange={(e) => {
|
||||||
|
editor.user.updateUserPreferences({
|
||||||
|
name: e.currentTarget.value,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { RecordsDiff, TLRecord } from "@tldraw/tldraw"
|
||||||
|
import _ from "lodash"
|
||||||
|
|
||||||
|
export function applyTLStoreChangesToAutomerge(
|
||||||
|
doc: any,
|
||||||
|
changes: RecordsDiff<TLRecord>
|
||||||
|
) {
|
||||||
|
Object.values(changes.added).forEach((record) => {
|
||||||
|
doc.store[record.id] = record
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.values(changes.updated).forEach(([_, record]) => {
|
||||||
|
deepCompareAndUpdate(doc.store[record.id], record)
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.values(changes.removed).forEach((record) => {
|
||||||
|
delete doc.store[record.id]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepCompareAndUpdate(objectA: any, objectB: any) {
|
||||||
|
// eslint-disable-line
|
||||||
|
if (_.isArray(objectB)) {
|
||||||
|
if (!_.isArray(objectA)) {
|
||||||
|
// if objectA is not an array, replace it with objectB
|
||||||
|
objectA = objectB.slice()
|
||||||
|
} else {
|
||||||
|
// compare and update array elements
|
||||||
|
for (let i = 0; i < objectB.length; i++) {
|
||||||
|
if (i >= objectA.length) {
|
||||||
|
objectA.push(objectB[i])
|
||||||
|
} else {
|
||||||
|
if (_.isObject(objectB[i]) || _.isArray(objectB[i])) {
|
||||||
|
// if element is an object or array, recursively compare and update
|
||||||
|
deepCompareAndUpdate(objectA[i], objectB[i])
|
||||||
|
} else if (objectA[i] !== objectB[i]) {
|
||||||
|
// update the element
|
||||||
|
objectA[i] = objectB[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove extra elements
|
||||||
|
if (objectA.length > objectB.length) {
|
||||||
|
objectA.splice(objectB.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_.isObject(objectB)) {
|
||||||
|
_.forIn(objectB, (value: any, key: any) => {
|
||||||
|
if (objectA[key] === undefined) {
|
||||||
|
// if key is not in objectA, add it
|
||||||
|
objectA[key] = value
|
||||||
|
} else {
|
||||||
|
if (_.isObject(value) || _.isArray(value)) {
|
||||||
|
// if value is an object or array, recursively compare and update
|
||||||
|
deepCompareAndUpdate(objectA[key], value)
|
||||||
|
} else if (objectA[key] !== value) {
|
||||||
|
// update the value
|
||||||
|
objectA[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
_.forIn(objectA, (_: any, key: string) => {
|
||||||
|
if ((objectB as any)[key] === undefined) {
|
||||||
|
// if key is not in objectB, remove it
|
||||||
|
delete objectA[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
export const DEFAULT_STORE = {
|
||||||
|
store: {
|
||||||
|
"document:document": {
|
||||||
|
gridSize: 10,
|
||||||
|
name: "",
|
||||||
|
meta: {},
|
||||||
|
id: "document:document",
|
||||||
|
typeName: "document",
|
||||||
|
},
|
||||||
|
"pointer:pointer": {
|
||||||
|
id: "pointer:pointer",
|
||||||
|
typeName: "pointer",
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
lastActivityTimestamp: 0,
|
||||||
|
meta: {},
|
||||||
|
},
|
||||||
|
"page:page": {
|
||||||
|
meta: {},
|
||||||
|
id: "page:page",
|
||||||
|
name: "Page 1",
|
||||||
|
index: "a1",
|
||||||
|
typeName: "page",
|
||||||
|
},
|
||||||
|
"camera:page:page": {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 1,
|
||||||
|
meta: {},
|
||||||
|
id: "camera:page:page",
|
||||||
|
typeName: "camera",
|
||||||
|
},
|
||||||
|
"instance_page_state:page:page": {
|
||||||
|
editingShapeId: null,
|
||||||
|
croppingShapeId: null,
|
||||||
|
selectedShapeIds: [],
|
||||||
|
hoveredShapeId: null,
|
||||||
|
erasingShapeIds: [],
|
||||||
|
hintingShapeIds: [],
|
||||||
|
focusedGroupId: null,
|
||||||
|
meta: {},
|
||||||
|
id: "instance_page_state:page:page",
|
||||||
|
pageId: "page:page",
|
||||||
|
typeName: "instance_page_state",
|
||||||
|
},
|
||||||
|
"instance:instance": {
|
||||||
|
followingUserId: null,
|
||||||
|
opacityForNextShape: 1,
|
||||||
|
stylesForNextShape: {},
|
||||||
|
brush: null,
|
||||||
|
scribble: null,
|
||||||
|
cursor: {
|
||||||
|
type: "default",
|
||||||
|
rotation: 0,
|
||||||
|
},
|
||||||
|
isFocusMode: false,
|
||||||
|
exportBackground: true,
|
||||||
|
isDebugMode: false,
|
||||||
|
isToolLocked: false,
|
||||||
|
screenBounds: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 720,
|
||||||
|
h: 400,
|
||||||
|
},
|
||||||
|
zoomBrush: null,
|
||||||
|
isGridMode: false,
|
||||||
|
isPenMode: false,
|
||||||
|
chatMessage: "",
|
||||||
|
isChatting: false,
|
||||||
|
highlightedUserIds: [],
|
||||||
|
canMoveCamera: true,
|
||||||
|
isFocused: true,
|
||||||
|
devicePixelRatio: 2,
|
||||||
|
isCoarsePointer: false,
|
||||||
|
isHoveringCanvas: false,
|
||||||
|
openMenus: [],
|
||||||
|
isChangingStyle: false,
|
||||||
|
isReadonly: false,
|
||||||
|
meta: {},
|
||||||
|
id: "instance:instance",
|
||||||
|
currentPageId: "page:page",
|
||||||
|
typeName: "instance",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
schemaVersion: 1,
|
||||||
|
storeVersion: 4,
|
||||||
|
recordVersions: {
|
||||||
|
asset: {
|
||||||
|
version: 1,
|
||||||
|
subTypeKey: "type",
|
||||||
|
subTypeVersions: {
|
||||||
|
image: 2,
|
||||||
|
video: 2,
|
||||||
|
bookmark: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
camera: {
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
document: {
|
||||||
|
version: 2,
|
||||||
|
},
|
||||||
|
instance: {
|
||||||
|
version: 21,
|
||||||
|
},
|
||||||
|
instance_page_state: {
|
||||||
|
version: 5,
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
version: 3,
|
||||||
|
subTypeKey: "type",
|
||||||
|
subTypeVersions: {
|
||||||
|
group: 0,
|
||||||
|
text: 1,
|
||||||
|
bookmark: 1,
|
||||||
|
draw: 1,
|
||||||
|
geo: 7,
|
||||||
|
note: 4,
|
||||||
|
line: 1,
|
||||||
|
frame: 0,
|
||||||
|
arrow: 1,
|
||||||
|
highlight: 0,
|
||||||
|
embed: 4,
|
||||||
|
image: 2,
|
||||||
|
video: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
instance_presence: {
|
||||||
|
version: 5,
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
import {
|
||||||
|
TLAnyShapeUtilConstructor,
|
||||||
|
TLRecord,
|
||||||
|
TLStoreWithStatus,
|
||||||
|
createTLStore,
|
||||||
|
defaultShapeUtils,
|
||||||
|
HistoryEntry,
|
||||||
|
getUserPreferences,
|
||||||
|
setUserPreferences,
|
||||||
|
defaultUserPreferences,
|
||||||
|
createPresenceStateDerivation,
|
||||||
|
InstancePresenceRecordType,
|
||||||
|
computed,
|
||||||
|
react,
|
||||||
|
TLStoreSnapshot,
|
||||||
|
sortById,
|
||||||
|
} from "@tldraw/tldraw"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { DocHandle, DocHandleChangePayload } from "@automerge/automerge-repo"
|
||||||
|
import {
|
||||||
|
useLocalAwareness,
|
||||||
|
useRemoteAwareness,
|
||||||
|
} from "@automerge/automerge-repo-react-hooks"
|
||||||
|
|
||||||
|
import { applyAutomergePatchesToTLStore } from "./AutomergeToTLStore.js"
|
||||||
|
import { applyTLStoreChangesToAutomerge } from "./TLStoreToAutomerge.js"
|
||||||
|
|
||||||
|
export function useAutomergeStore({
|
||||||
|
handle,
|
||||||
|
shapeUtils = [],
|
||||||
|
}: {
|
||||||
|
handle: DocHandle<TLStoreSnapshot>
|
||||||
|
userId: string
|
||||||
|
shapeUtils?: TLAnyShapeUtilConstructor[]
|
||||||
|
}): TLStoreWithStatus {
|
||||||
|
const [store] = useState(() => {
|
||||||
|
const store = createTLStore({
|
||||||
|
shapeUtils: [...defaultShapeUtils, ...shapeUtils],
|
||||||
|
})
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
|
||||||
|
const [storeWithStatus, setStoreWithStatus] = useState<TLStoreWithStatus>({
|
||||||
|
status: "loading",
|
||||||
|
})
|
||||||
|
|
||||||
|
/* -------------------- TLDraw <--> Automerge -------------------- */
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubs: (() => void)[] = []
|
||||||
|
|
||||||
|
// A hacky workaround to prevent local changes from being applied twice
|
||||||
|
// once into the automerge doc and then back again.
|
||||||
|
let preventPatchApplications = false
|
||||||
|
|
||||||
|
/* TLDraw to Automerge */
|
||||||
|
function syncStoreChangesToAutomergeDoc({
|
||||||
|
changes,
|
||||||
|
}: HistoryEntry<TLRecord>) {
|
||||||
|
preventPatchApplications = true
|
||||||
|
handle.change((doc) => {
|
||||||
|
applyTLStoreChangesToAutomerge(doc, changes)
|
||||||
|
})
|
||||||
|
preventPatchApplications = false
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubs.push(
|
||||||
|
store.listen(syncStoreChangesToAutomergeDoc, {
|
||||||
|
source: "user",
|
||||||
|
scope: "document",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Automerge to TLDraw */
|
||||||
|
const syncAutomergeDocChangesToStore = ({
|
||||||
|
patches,
|
||||||
|
}: DocHandleChangePayload<any>) => {
|
||||||
|
if (preventPatchApplications) return
|
||||||
|
|
||||||
|
applyAutomergePatchesToTLStore(patches, store)
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.on("change", syncAutomergeDocChangesToStore)
|
||||||
|
unsubs.push(() => handle.off("change", syncAutomergeDocChangesToStore))
|
||||||
|
|
||||||
|
/* Defer rendering until the document is ready */
|
||||||
|
// TODO: need to think through the various status possibilities here and how they map
|
||||||
|
handle.whenReady().then(() => {
|
||||||
|
const doc = handle.docSync()
|
||||||
|
if (!doc) throw new Error("Document not found")
|
||||||
|
if (!doc.store) throw new Error("Document store not initialized")
|
||||||
|
|
||||||
|
store.mergeRemoteChanges(() => {
|
||||||
|
store.loadSnapshot({
|
||||||
|
store: JSON.parse(JSON.stringify(doc.store)),
|
||||||
|
schema: doc.schema,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
setStoreWithStatus({
|
||||||
|
store,
|
||||||
|
status: "synced-remote",
|
||||||
|
connectionStatus: "online",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubs.forEach((fn) => fn())
|
||||||
|
unsubs.length = 0
|
||||||
|
}
|
||||||
|
}, [handle, store])
|
||||||
|
|
||||||
|
return storeWithStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAutomergePresence({ handle, store, userMetadata }:
|
||||||
|
{ handle: DocHandle<TLStoreSnapshot>, store: TLStoreWithStatus, userMetadata: any }) {
|
||||||
|
|
||||||
|
const innerStore = store?.store
|
||||||
|
|
||||||
|
const { userId, name, color } = userMetadata
|
||||||
|
|
||||||
|
const [, updateLocalState] = useLocalAwareness({
|
||||||
|
handle,
|
||||||
|
userId,
|
||||||
|
initialState: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const [peerStates] = useRemoteAwareness({
|
||||||
|
handle,
|
||||||
|
localUserId: userId,
|
||||||
|
})
|
||||||
|
|
||||||
|
/* ----------- Presence stuff ----------- */
|
||||||
|
useEffect(() => {
|
||||||
|
if (!innerStore) return
|
||||||
|
|
||||||
|
const toPut: TLRecord[] =
|
||||||
|
Object.values(peerStates)
|
||||||
|
.filter((record) => record && Object.keys(record).length !== 0)
|
||||||
|
|
||||||
|
// put / remove the records in the store
|
||||||
|
const toRemove = innerStore.query.records('instance_presence').get().sort(sortById)
|
||||||
|
.map((record) => record.id)
|
||||||
|
.filter((id) => !toPut.find((record) => record.id === id))
|
||||||
|
|
||||||
|
if (toRemove.length) innerStore.remove(toRemove)
|
||||||
|
if (toPut.length) innerStore.put(toPut)
|
||||||
|
}, [innerStore, peerStates])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!innerStore) return
|
||||||
|
/* ----------- Presence stuff ----------- */
|
||||||
|
setUserPreferences({ id: userId, color, name })
|
||||||
|
|
||||||
|
const userPreferences = computed<{
|
||||||
|
id: string
|
||||||
|
color: string
|
||||||
|
name: string
|
||||||
|
}>("userPreferences", () => {
|
||||||
|
const user = getUserPreferences()
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
color: user.color ?? defaultUserPreferences.color,
|
||||||
|
name: user.name ?? defaultUserPreferences.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const presenceId = InstancePresenceRecordType.createId(userId)
|
||||||
|
const presenceDerivation = createPresenceStateDerivation(
|
||||||
|
userPreferences,
|
||||||
|
presenceId
|
||||||
|
)(innerStore)
|
||||||
|
|
||||||
|
return react("when presence changes", () => {
|
||||||
|
const presence = presenceDerivation.get()
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
updateLocalState(presence)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, [innerStore, userId, updateLocalState])
|
||||||
|
/* ----------- End presence stuff ----------- */
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue