···
1
1
{
2
2
-
"name": "jazz-drawings2",
2
2
+
"name": "jazz-endless-canvas",
3
3
"version": "0.0.1",
4
4
"lockfileVersion": 3,
5
5
"requires": true,
6
6
"packages": {
7
7
"": {
8
8
-
"name": "jazz-drawings2",
8
8
+
"name": "jazz-endless-canvas",
9
9
"version": "0.0.1",
10
10
"license": "MIT",
11
11
"dependencies": {
···
15
15
"@sveltejs/adapter-static": "^3.0.8",
16
16
"@use-gesture/vanilla": "^10.3.1",
17
17
"jazz-inspector-element": "^0.14.0",
18
18
-
"jazz-svelte": "^0.14.0",
19
19
-
"jazz-tools": "^0.14.0",
18
18
+
"jazz-svelte": "^0.14.4",
19
19
+
"jazz-tools": "^0.14.4",
20
20
"paper": "^0.12.18"
21
21
},
22
22
"devDependencies": {
···
2222
2222
}
2223
2223
},
2224
2224
"node_modules/cojson-storage": {
2225
2225
-
"version": "0.14.0",
2226
2226
-
"resolved": "https://registry.npmjs.org/cojson-storage/-/cojson-storage-0.14.0.tgz",
2227
2227
-
"integrity": "sha512-DA7fhc4f95ysp8VMX8jAs+si1W55tc1RWAg+QK34ZBwXgheBpyjAAQ/bJewHm7gqvPKJo8B76NBsVscsm7T/xA==",
2225
2225
+
"version": "0.14.1",
2226
2226
+
"resolved": "https://registry.npmjs.org/cojson-storage/-/cojson-storage-0.14.1.tgz",
2227
2227
+
"integrity": "sha512-65VQpJpON5wl6XivkJLtMxW5MRv2+NFCbqxuJDNkP98F4UTTvQoVWzDkgMqOkDwizPS9UNbSnZ/N7i42Cw+cmA==",
2228
2228
"license": "MIT",
2229
2229
"dependencies": {
2230
2230
-
"cojson": "0.14.0"
2230
2230
+
"cojson": "0.14.1"
2231
2231
}
2232
2232
},
2233
2233
"node_modules/cojson-storage-indexeddb": {
2234
2234
-
"version": "0.14.0",
2235
2235
-
"resolved": "https://registry.npmjs.org/cojson-storage-indexeddb/-/cojson-storage-indexeddb-0.14.0.tgz",
2236
2236
-
"integrity": "sha512-wpfgdIbiLH1rXJqEMTV9wgQ+v6YLZ41kDS5hVYIUaacFNt3GBQO24xMl4L34m+UK2tqxKvza8EAXqeUpD3C53g==",
2234
2234
+
"version": "0.14.1",
2235
2235
+
"resolved": "https://registry.npmjs.org/cojson-storage-indexeddb/-/cojson-storage-indexeddb-0.14.1.tgz",
2236
2236
+
"integrity": "sha512-yCX72QWaAII0Hb4BBq+mF2pUUfUCjy/C8c6e8Zk2Vz3G1O4dC1hV2o+WZokVpBXhddClPvEHzUaSpeIQMTNB0w==",
2237
2237
+
"license": "MIT",
2238
2238
+
"dependencies": {
2239
2239
+
"cojson": "0.14.1",
2240
2240
+
"cojson-storage": "0.14.1"
2241
2241
+
}
2242
2242
+
},
2243
2243
+
"node_modules/cojson-storage-indexeddb/node_modules/cojson": {
2244
2244
+
"version": "0.14.1",
2245
2245
+
"resolved": "https://registry.npmjs.org/cojson/-/cojson-0.14.1.tgz",
2246
2246
+
"integrity": "sha512-U3VhymNJqx56mt61UZT6ey6dDp53Qjq2390//ARBg5FW9qpebg03AqvWQjAZoadmO4QVgfLXAzkZzc5NoEFUMg==",
2247
2247
+
"license": "MIT",
2248
2248
+
"dependencies": {
2249
2249
+
"@noble/ciphers": "^1.3.0",
2250
2250
+
"@noble/curves": "^1.9.1",
2251
2251
+
"@noble/hashes": "^1.8.0",
2252
2252
+
"@opentelemetry/api": "^1.9.0",
2253
2253
+
"@scure/base": "1.2.1",
2254
2254
+
"jazz-crypto-rs": "0.0.7",
2255
2255
+
"neverthrow": "^7.0.1",
2256
2256
+
"queueueue": "^4.1.2",
2257
2257
+
"unicode-segmenter": "^0.12.0"
2258
2258
+
}
2259
2259
+
},
2260
2260
+
"node_modules/cojson-storage/node_modules/cojson": {
2261
2261
+
"version": "0.14.1",
2262
2262
+
"resolved": "https://registry.npmjs.org/cojson/-/cojson-0.14.1.tgz",
2263
2263
+
"integrity": "sha512-U3VhymNJqx56mt61UZT6ey6dDp53Qjq2390//ARBg5FW9qpebg03AqvWQjAZoadmO4QVgfLXAzkZzc5NoEFUMg==",
2237
2264
"license": "MIT",
2238
2265
"dependencies": {
2239
2239
-
"cojson": "0.14.0",
2240
2240
-
"cojson-storage": "0.14.0"
2266
2266
+
"@noble/ciphers": "^1.3.0",
2267
2267
+
"@noble/curves": "^1.9.1",
2268
2268
+
"@noble/hashes": "^1.8.0",
2269
2269
+
"@opentelemetry/api": "^1.9.0",
2270
2270
+
"@scure/base": "1.2.1",
2271
2271
+
"jazz-crypto-rs": "0.0.7",
2272
2272
+
"neverthrow": "^7.0.1",
2273
2273
+
"queueueue": "^4.1.2",
2274
2274
+
"unicode-segmenter": "^0.12.0"
2241
2275
}
2242
2276
},
2243
2277
"node_modules/cojson-transport-ws": {
2244
2244
-
"version": "0.14.0",
2245
2245
-
"resolved": "https://registry.npmjs.org/cojson-transport-ws/-/cojson-transport-ws-0.14.0.tgz",
2246
2246
-
"integrity": "sha512-PhM15u2pxQ6vVsW9sn9ZfTPLOO6W61peMXv+4nRiewZVZ2z3VsspMcxhiFE7QUxD3d0KGK37U5eUcXEwcjhPLg==",
2278
2278
+
"version": "0.14.1",
2279
2279
+
"resolved": "https://registry.npmjs.org/cojson-transport-ws/-/cojson-transport-ws-0.14.1.tgz",
2280
2280
+
"integrity": "sha512-GpCBm5oqn0AHbIW/OX/bqVzlcJcAPOunk2mt1JK/u2BcSzebeu0luU1AG54k/0GUxX/ZbrgmdqeOxPi6QWXZbw==",
2247
2281
"license": "MIT",
2248
2282
"dependencies": {
2249
2249
-
"cojson": "0.14.0"
2283
2283
+
"cojson": "0.14.1"
2284
2284
+
}
2285
2285
+
},
2286
2286
+
"node_modules/cojson-transport-ws/node_modules/cojson": {
2287
2287
+
"version": "0.14.1",
2288
2288
+
"resolved": "https://registry.npmjs.org/cojson/-/cojson-0.14.1.tgz",
2289
2289
+
"integrity": "sha512-U3VhymNJqx56mt61UZT6ey6dDp53Qjq2390//ARBg5FW9qpebg03AqvWQjAZoadmO4QVgfLXAzkZzc5NoEFUMg==",
2290
2290
+
"license": "MIT",
2291
2291
+
"dependencies": {
2292
2292
+
"@noble/ciphers": "^1.3.0",
2293
2293
+
"@noble/curves": "^1.9.1",
2294
2294
+
"@noble/hashes": "^1.8.0",
2295
2295
+
"@opentelemetry/api": "^1.9.0",
2296
2296
+
"@scure/base": "1.2.1",
2297
2297
+
"jazz-crypto-rs": "0.0.7",
2298
2298
+
"neverthrow": "^7.0.1",
2299
2299
+
"queueueue": "^4.1.2",
2300
2300
+
"unicode-segmenter": "^0.12.0"
2250
2301
}
2251
2302
},
2252
2303
"node_modules/color-convert": {
···
3013
3064
"license": "MIT"
3014
3065
},
3015
3066
"node_modules/jazz-browser": {
3016
3016
-
"version": "0.14.0",
3017
3017
-
"resolved": "https://registry.npmjs.org/jazz-browser/-/jazz-browser-0.14.0.tgz",
3018
3018
-
"integrity": "sha512-V2/rBYB2oVq70MurpkkQ/3Pms0NrebV5Ru4cJfvdl4hvBCQYK1wM0hHDA4HWXq9oLBbs6NsXrmI8BRsEHAWgkg==",
3067
3067
+
"version": "0.14.4",
3068
3068
+
"resolved": "https://registry.npmjs.org/jazz-browser/-/jazz-browser-0.14.4.tgz",
3069
3069
+
"integrity": "sha512-jQFltPwjPYg1S2/aH1ORCdajn7v9NlTPc8eGOonYIGciR25Jra6W1R770DQJDpTZeKtxjyBEH9utydvk7c+iEg==",
3019
3070
"license": "MIT",
3020
3071
"dependencies": {
3021
3021
-
"cojson": "0.14.0",
3022
3022
-
"cojson-storage-indexeddb": "0.14.0",
3023
3023
-
"cojson-transport-ws": "0.14.0",
3024
3024
-
"jazz-tools": "0.14.0"
3072
3072
+
"cojson": "0.14.1",
3073
3073
+
"cojson-storage-indexeddb": "0.14.1",
3074
3074
+
"cojson-transport-ws": "0.14.1",
3075
3075
+
"jazz-tools": "0.14.4"
3076
3076
+
}
3077
3077
+
},
3078
3078
+
"node_modules/jazz-browser/node_modules/cojson": {
3079
3079
+
"version": "0.14.1",
3080
3080
+
"resolved": "https://registry.npmjs.org/cojson/-/cojson-0.14.1.tgz",
3081
3081
+
"integrity": "sha512-U3VhymNJqx56mt61UZT6ey6dDp53Qjq2390//ARBg5FW9qpebg03AqvWQjAZoadmO4QVgfLXAzkZzc5NoEFUMg==",
3082
3082
+
"license": "MIT",
3083
3083
+
"dependencies": {
3084
3084
+
"@noble/ciphers": "^1.3.0",
3085
3085
+
"@noble/curves": "^1.9.1",
3086
3086
+
"@noble/hashes": "^1.8.0",
3087
3087
+
"@opentelemetry/api": "^1.9.0",
3088
3088
+
"@scure/base": "1.2.1",
3089
3089
+
"jazz-crypto-rs": "0.0.7",
3090
3090
+
"neverthrow": "^7.0.1",
3091
3091
+
"queueueue": "^4.1.2",
3092
3092
+
"unicode-segmenter": "^0.12.0"
3025
3093
}
3026
3094
},
3027
3095
"node_modules/jazz-crypto-rs": {
···
3055
3123
"react-dom": "^18.3.1"
3056
3124
}
3057
3125
},
3126
3126
+
"node_modules/jazz-inspector-element/node_modules/jazz-tools": {
3127
3127
+
"version": "0.14.0",
3128
3128
+
"resolved": "https://registry.npmjs.org/jazz-tools/-/jazz-tools-0.14.0.tgz",
3129
3129
+
"integrity": "sha512-ioshDuFQtZTz0ovSG0B0osVXPxW/s0XFIiFPheRd2bKWkJAChyRFsIegG6XI9FWnsmiTX8/PhtJ74yXik04gXQ==",
3130
3130
+
"license": "MIT",
3131
3131
+
"dependencies": {
3132
3132
+
"@scure/bip39": "^1.3.0",
3133
3133
+
"cojson": "0.14.0",
3134
3134
+
"fast-myers-diff": "^3.2.0",
3135
3135
+
"zod": "3.25.0-beta.20250518T002810"
3136
3136
+
}
3137
3137
+
},
3138
3138
+
"node_modules/jazz-inspector/node_modules/jazz-tools": {
3139
3139
+
"version": "0.14.0",
3140
3140
+
"resolved": "https://registry.npmjs.org/jazz-tools/-/jazz-tools-0.14.0.tgz",
3141
3141
+
"integrity": "sha512-ioshDuFQtZTz0ovSG0B0osVXPxW/s0XFIiFPheRd2bKWkJAChyRFsIegG6XI9FWnsmiTX8/PhtJ74yXik04gXQ==",
3142
3142
+
"license": "MIT",
3143
3143
+
"dependencies": {
3144
3144
+
"@scure/bip39": "^1.3.0",
3145
3145
+
"cojson": "0.14.0",
3146
3146
+
"fast-myers-diff": "^3.2.0",
3147
3147
+
"zod": "3.25.0-beta.20250518T002810"
3148
3148
+
}
3149
3149
+
},
3058
3150
"node_modules/jazz-react-core": {
3059
3151
"version": "0.14.0",
3060
3152
"resolved": "https://registry.npmjs.org/jazz-react-core/-/jazz-react-core-0.14.0.tgz",
···
3069
3161
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
3070
3162
}
3071
3163
},
3072
3072
-
"node_modules/jazz-svelte": {
3164
3164
+
"node_modules/jazz-react-core/node_modules/jazz-tools": {
3073
3165
"version": "0.14.0",
3074
3074
-
"resolved": "https://registry.npmjs.org/jazz-svelte/-/jazz-svelte-0.14.0.tgz",
3075
3075
-
"integrity": "sha512-8GZ6eTKi3gtlIb8hlIeTgZyX+Y872tpoUDdIxSmHXHJRRsO+0KD+cFIqkIxOPv7uCqT2eZgnTQi47YcWInsHJw==",
3166
3166
+
"resolved": "https://registry.npmjs.org/jazz-tools/-/jazz-tools-0.14.0.tgz",
3167
3167
+
"integrity": "sha512-ioshDuFQtZTz0ovSG0B0osVXPxW/s0XFIiFPheRd2bKWkJAChyRFsIegG6XI9FWnsmiTX8/PhtJ74yXik04gXQ==",
3168
3168
+
"license": "MIT",
3076
3169
"dependencies": {
3170
3170
+
"@scure/bip39": "^1.3.0",
3077
3171
"cojson": "0.14.0",
3078
3078
-
"jazz-browser": "0.14.0",
3079
3079
-
"jazz-tools": "0.14.0"
3172
3172
+
"fast-myers-diff": "^3.2.0",
3173
3173
+
"zod": "3.25.0-beta.20250518T002810"
3174
3174
+
}
3175
3175
+
},
3176
3176
+
"node_modules/jazz-svelte": {
3177
3177
+
"version": "0.14.4",
3178
3178
+
"resolved": "https://registry.npmjs.org/jazz-svelte/-/jazz-svelte-0.14.4.tgz",
3179
3179
+
"integrity": "sha512-Pj7GzN3CyAYOG84D2weAOg6RCIKEeLJXMcyVIhzuRQybOQH5LBLGsQM5mz3k6BMAsVJ0Lya4nPbk6GNqbq7AWg==",
3180
3180
+
"dependencies": {
3181
3181
+
"cojson": "0.14.1",
3182
3182
+
"jazz-browser": "0.14.4",
3183
3183
+
"jazz-tools": "0.14.4"
3080
3184
},
3081
3185
"peerDependencies": {
3082
3186
"svelte": "^5.0.0"
3083
3187
}
3084
3188
},
3189
3189
+
"node_modules/jazz-svelte/node_modules/cojson": {
3190
3190
+
"version": "0.14.1",
3191
3191
+
"resolved": "https://registry.npmjs.org/cojson/-/cojson-0.14.1.tgz",
3192
3192
+
"integrity": "sha512-U3VhymNJqx56mt61UZT6ey6dDp53Qjq2390//ARBg5FW9qpebg03AqvWQjAZoadmO4QVgfLXAzkZzc5NoEFUMg==",
3193
3193
+
"license": "MIT",
3194
3194
+
"dependencies": {
3195
3195
+
"@noble/ciphers": "^1.3.0",
3196
3196
+
"@noble/curves": "^1.9.1",
3197
3197
+
"@noble/hashes": "^1.8.0",
3198
3198
+
"@opentelemetry/api": "^1.9.0",
3199
3199
+
"@scure/base": "1.2.1",
3200
3200
+
"jazz-crypto-rs": "0.0.7",
3201
3201
+
"neverthrow": "^7.0.1",
3202
3202
+
"queueueue": "^4.1.2",
3203
3203
+
"unicode-segmenter": "^0.12.0"
3204
3204
+
}
3205
3205
+
},
3085
3206
"node_modules/jazz-tools": {
3086
3086
-
"version": "0.14.0",
3087
3087
-
"resolved": "https://registry.npmjs.org/jazz-tools/-/jazz-tools-0.14.0.tgz",
3088
3088
-
"integrity": "sha512-ioshDuFQtZTz0ovSG0B0osVXPxW/s0XFIiFPheRd2bKWkJAChyRFsIegG6XI9FWnsmiTX8/PhtJ74yXik04gXQ==",
3207
3207
+
"version": "0.14.4",
3208
3208
+
"resolved": "https://registry.npmjs.org/jazz-tools/-/jazz-tools-0.14.4.tgz",
3209
3209
+
"integrity": "sha512-DmVWpExJTBpoYlJuPCAn/cPv1J25IQFHd69mCvsQUpuDHKYZOIOP2B8PJDS3TQLTUwKwHCbCGenG0NqK2SAFCQ==",
3089
3210
"license": "MIT",
3090
3211
"dependencies": {
3091
3212
"@scure/bip39": "^1.3.0",
3092
3092
-
"cojson": "0.14.0",
3213
3213
+
"cojson": "0.14.1",
3093
3214
"fast-myers-diff": "^3.2.0",
3094
3215
"zod": "3.25.0-beta.20250518T002810"
3216
3216
+
}
3217
3217
+
},
3218
3218
+
"node_modules/jazz-tools/node_modules/cojson": {
3219
3219
+
"version": "0.14.1",
3220
3220
+
"resolved": "https://registry.npmjs.org/cojson/-/cojson-0.14.1.tgz",
3221
3221
+
"integrity": "sha512-U3VhymNJqx56mt61UZT6ey6dDp53Qjq2390//ARBg5FW9qpebg03AqvWQjAZoadmO4QVgfLXAzkZzc5NoEFUMg==",
3222
3222
+
"license": "MIT",
3223
3223
+
"dependencies": {
3224
3224
+
"@noble/ciphers": "^1.3.0",
3225
3225
+
"@noble/curves": "^1.9.1",
3226
3226
+
"@noble/hashes": "^1.8.0",
3227
3227
+
"@opentelemetry/api": "^1.9.0",
3228
3228
+
"@scure/base": "1.2.1",
3229
3229
+
"jazz-crypto-rs": "0.0.7",
3230
3230
+
"neverthrow": "^7.0.1",
3231
3231
+
"queueueue": "^4.1.2",
3232
3232
+
"unicode-segmenter": "^0.12.0"
3095
3233
}
3096
3234
},
3097
3235
"node_modules/jiti": {
···
43
43
"@sveltejs/adapter-static": "^3.0.8",
44
44
"@use-gesture/vanilla": "^10.3.1",
45
45
"jazz-inspector-element": "^0.14.0",
46
46
-
"jazz-svelte": "^0.14.0",
47
47
-
"jazz-tools": "^0.14.0",
46
46
+
"jazz-svelte": "^0.14.4",
47
47
+
"jazz-tools": "^0.14.4",
48
48
"paper": "^0.12.18"
49
49
},
50
50
"license": "MIT"
···
1
1
-
import { coField, CoMap, CoList } from 'jazz-tools';
1
1
+
import { co, z } from 'jazz-tools';
2
2
3
3
-
export class Point extends CoMap {
4
4
-
x = coField.number;
5
5
-
y = coField.number;
6
6
-
}
3
3
+
const Point = z.object({
4
4
+
x: z.number(),
5
5
+
y: z.number()
6
6
+
});
7
7
8
8
-
export class Segment extends CoMap {
9
9
-
point = coField.ref(Point);
10
10
-
handleIn = coField.ref(Point);
11
11
-
handleOut = coField.ref(Point);
12
12
-
}
8
8
+
export const Path = co.map({
9
9
+
segments: z.array(
10
10
+
z.object({
11
11
+
point: Point,
12
12
+
handleIn: Point,
13
13
+
handleOut: Point
14
14
+
})
15
15
+
),
16
16
+
strokeColor: z.string(),
17
17
+
strokeWidth: z.number()
18
18
+
});
13
19
14
14
-
export class Segments extends CoList.Of(coField.ref(Segment)) {}
20
20
+
export const PaintingPaths = co.list(Path);
15
21
16
16
-
export class Path extends CoMap {
17
17
-
segments = coField.ref(Segments);
18
18
-
strokeColor = coField.string;
19
19
-
strokeWidth = coField.number;
20
20
-
}
22
22
+
export const PaintingCell = co.map({
23
23
+
paths: PaintingPaths
24
24
+
});
25
25
+
26
26
+
export const PaintingCells = co.record(z.string(), PaintingCell);
27
27
+
28
28
+
export const Painting = co.map({
29
29
+
cells: PaintingCells
30
30
+
});
31
31
+
32
32
+
export const MyAppAccount = co.account({
33
33
+
profile: co.profile(),
34
34
+
root: co.map({})
35
35
+
});
21
36
22
22
-
export class PaintingPaths extends CoList.Of(coField.ref(Path)) {}
37
37
+
export const cellSize = 1000;
23
38
24
24
-
export class Counter extends CoMap {
25
25
-
painting = coField.ref(PaintingPaths);
26
26
-
count = coField.number;
27
27
-
}
39
39
+
export const gridId = (x: number, y: number): string => {
40
40
+
return `${Math.floor(x)}/${Math.floor(y)}`;
41
41
+
};
28
42
29
29
-
export class Paintings extends CoList.Of(coField.ref(Counter)) {}
43
43
+
export const indexFromGridId = (gridId: string): { x: number; y: number } | undefined => {
44
44
+
if (!gridId?.includes('/')) return undefined;
45
45
+
const [xIndex, yIndex] = gridId.split('/').map(Number);
46
46
+
return { x: xIndex, y: yIndex };
47
47
+
};
···
1
1
<script lang="ts">
2
2
-
import '../app.css';
3
2
import { Head, Toaster } from '@fuxui/base';
4
3
import { JazzProvider } from 'jazz-svelte';
5
5
-
import { Account } from 'jazz-tools';
4
4
+
import { MyAppAccount } from '$lib/schema';
5
5
+
import '../app.css';
6
6
7
7
let { children } = $props();
8
8
</script>
···
11
11
sync={{
12
12
peer: `wss://cloud.jazz.tools/?key=flo.bit.dev@gmail.com`
13
13
}}
14
14
-
AccountSchema={Account}
14
14
+
AccountSchema={MyAppAccount}
15
15
>
16
16
17
17
{@render children()}
···
1
1
<script>
2
2
-
import Draw from './Draw.svelte';
3
2
import { CoState } from 'jazz-svelte';
4
4
-
import { Counter, PaintingPaths } from '$lib/schema';
3
3
+
import {
4
4
+
gridId,
5
5
+
Painting,
6
6
+
PaintingCell,
7
7
+
PaintingCells,
8
8
+
PaintingPaths
9
9
+
} from '$lib/schema';
10
10
+
import { Button, Modal, Prose } from '@fuxui/base';
11
11
+
import Draw from './Draw.svelte';
5
12
import { Group } from 'jazz-tools';
6
6
-
import { Alert, Button, Heading, Modal, Paragraph, Prose } from '@fuxui/base';
7
13
8
14
// $effect(() => {
9
9
-
// console.log('painting', painting);
15
15
+
// console.log('painting.current', painting.current);
10
16
// if (painting.current === undefined) {
11
17
// const group = Group.create();
12
18
// group.addMember('everyone', 'writer');
13
19
14
14
-
// // create new
15
15
-
// let newPainting = Counter.create(
20
20
+
// const id = gridId(0, 0);
21
21
+
// let newPainting = Painting.create(
16
22
// {
17
17
-
// painting: PaintingPaths.create([], group),
18
18
-
// count: 0
23
23
+
// cells: PaintingCells.create(
24
24
+
// {
25
25
+
// [id]: PaintingCell.create(
26
26
+
// {
27
27
+
// paths: PaintingPaths.create([], group)
28
28
+
// },
29
29
+
// group
30
30
+
// )
31
31
+
// },
32
32
+
// group
33
33
+
// )
19
34
// },
20
35
// group
21
36
// );
···
24
39
// }
25
40
// });
26
41
42
42
+
// co_zfasJeym9uvhBJ9mKnnaNJAVyjp
43
43
+
27
44
let painting = $derived(
28
28
-
new CoState(Counter, 'co_zfasJeym9uvhBJ9mKnnaNJAVyjp', {
29
29
-
resolve: {
30
30
-
painting: {
31
31
-
$each: {
32
32
-
segments: {
33
33
-
$each: true
34
34
-
}
35
35
-
}
36
36
-
}
37
37
-
}
38
38
-
})
45
45
+
new CoState(Painting, 'co_zWoTxwCotTMzdfP4hNSuRKU3X33')
39
46
);
40
47
41
48
let isOpen = $state(true);
42
49
</script>
43
50
44
44
-
<Draw {painting} />
51
51
+
<Draw painting={painting.current} />
45
52
46
53
<Button onclick={() => (isOpen = true)} class="fixed bottom-2 left-2 z-10" variant="secondary">
47
54
Info
···
56
63
<h3>Controls:</h3>
57
64
58
65
<ul>
59
59
-
<li>Click "Random point" to jump to a random drawn point in the canvas</li>
60
66
<li>
61
67
Move around by selecting the "move" tool and dragging the canvas around (or use w, a, s, d)
62
68
</li>
···
1
1
<script lang="ts">
2
2
-
import { onMount } from 'svelte';
2
2
+
import { onDestroy, onMount } from 'svelte';
3
3
import paper from 'paper';
4
4
-
import { Counter, PaintingPaths, Path, Point, Segment, Segments } from '$lib/schema';
5
5
-
import { CoState } from 'jazz-svelte';
6
6
-
import { Group } from 'jazz-tools';
4
4
+
import { Group, type Loaded } from 'jazz-tools';
7
5
import { ColorPicker } from '@fuxui/colors';
8
6
import {
7
7
+
Box,
9
8
Button,
10
9
cn,
11
10
Popover,
12
11
SliderNumber,
13
12
Subheading,
14
14
-
toast,
15
13
ToggleGroup,
16
14
ToggleGroupItem
17
15
} from '@fuxui/base';
18
18
-
import { base } from '$app/paths';
16
16
+
import NumberFlow from '@number-flow/svelte';
19
17
20
18
import { PinchGesture } from '@use-gesture/vanilla';
19
19
+
import { cellSize, gridId, PaintingCell, PaintingPaths, Path, Painting } from '$lib/schema';
21
20
22
22
-
let { painting }: { painting: CoState<Counter> } = $props();
21
21
+
let { painting }: { painting: Loaded<typeof Painting> } = $props();
23
22
24
24
-
let tool: paper.Tool | null = null;
23
23
+
let moveTool: paper.Tool | null = null;
24
24
+
let drawTool: paper.Tool | null = null;
25
25
+
25
26
let path: paper.Path | null = null;
26
27
let scope: paper.PaperScope | null = null;
27
27
-
let drawnPaths: { [key: string]: paper.Path } = $state({});
28
28
let currentColor = $state('#ec4899');
29
29
-
let subscription = $state(null);
30
29
31
31
-
$effect(() => {
32
32
-
if (subscription || !painting.current?.painting) return;
30
30
+
let loaded = $state(false);
33
31
34
34
-
PaintingPaths.subscribe(
35
35
-
painting.current.painting.id,
32
32
+
let gridData: Record<
33
33
+
string,
34
34
+
{
35
35
+
unsubscribe?: () => void;
36
36
+
drawnPaths?: Record<string, paper.Path>;
37
37
+
group?: paper.Group;
38
38
+
}
39
39
+
> = $state({});
40
40
+
41
41
+
function addSubscription(id: string) {
42
42
+
if (!painting || !painting.cells || !painting.cells[id]?.paths || gridData[id]?.unsubscribe) {
43
43
+
return;
44
44
+
}
45
45
+
46
46
+
const sub = PaintingPaths.subscribe(
47
47
+
painting.cells[id]?.paths.id,
36
48
{
37
49
resolve: {
38
38
-
$each: {
39
39
-
segments: {
40
40
-
$each: {
41
41
-
point: true,
42
42
-
handleIn: true,
43
43
-
handleOut: true
44
44
-
}
45
45
-
}
46
46
-
}
50
50
+
$each: true
47
51
}
48
52
},
49
53
(painting) => {
50
50
-
// get all paths
51
51
-
for (let path of painting ?? []) {
52
52
-
if (!path || drawnPaths[path.id]) continue;
54
54
+
addPath(painting, id);
55
55
+
}
56
56
+
);
57
57
+
// console.log('added subscription', id);
58
58
+
59
59
+
gridData[id] ??= {};
60
60
+
61
61
+
if (gridData[id].group) {
62
62
+
gridData[id].group.visible = true;
63
63
+
}
64
64
+
65
65
+
gridData[id].unsubscribe = sub;
66
66
+
}
67
67
+
68
68
+
function removeSubscription(id: string) {
69
69
+
if (!gridData[id]?.unsubscribe) return;
70
70
+
71
71
+
gridData[id].unsubscribe();
72
72
+
// console.log('removed subscription', id);
73
73
+
gridData[id].unsubscribe = undefined;
74
74
+
75
75
+
if (gridData[id].group) {
76
76
+
gridData[id].group.visible = false;
77
77
+
}
78
78
+
}
79
79
+
80
80
+
function addPath(painting: Loaded<typeof PaintingPaths>, id: string) {
81
81
+
loaded = true;
82
82
+
83
83
+
gridData[id] ??= {};
84
84
+
gridData[id].drawnPaths ??= {};
85
85
+
gridData[id].group ??= new paper.Group();
86
86
+
87
87
+
let drawnPaths = gridData[id].drawnPaths;
88
88
+
let group = gridData[id].group;
53
89
54
54
-
drawnPaths[path.id] = new paper.Path();
55
55
-
drawnPaths[path.id].strokeColor = path.strokeColor as unknown as paper.Color;
56
56
-
drawnPaths[path.id].strokeWidth = path.strokeWidth;
90
90
+
for (let path of painting ?? []) {
91
91
+
if (!path || drawnPaths[path.id]) continue;
92
92
+
93
93
+
drawnPaths[path.id] = new paper.Path();
94
94
+
drawnPaths[path.id].strokeColor = path.strokeColor as unknown as paper.Color;
95
95
+
drawnPaths[path.id].strokeWidth = path.strokeWidth;
96
96
+
97
97
+
group.addChild(drawnPaths[path.id]);
57
98
58
58
-
for (let segment of path.segments ?? []) {
59
59
-
if (!segment) continue;
99
99
+
for (let segment of path.segments ?? []) {
100
100
+
if (!segment) continue;
60
101
61
61
-
scope?.activate();
102
102
+
scope?.activate();
62
103
63
63
-
let segmentPoint = new paper.Segment({
64
64
-
point: new paper.Point(segment.point.x, segment.point.y),
65
65
-
handleIn: new paper.Point(segment.handleIn.x, segment.handleIn.y),
66
66
-
handleOut: new paper.Point(segment.handleOut.x, segment.handleOut.y)
67
67
-
});
104
104
+
let segmentPoint = new paper.Segment({
105
105
+
point: new paper.Point(segment.point.x, segment.point.y),
106
106
+
handleIn: new paper.Point(segment.handleIn.x, segment.handleIn.y),
107
107
+
handleOut: new paper.Point(segment.handleOut.x, segment.handleOut.y)
108
108
+
});
68
109
69
69
-
drawnPaths[path.id].add(segmentPoint);
70
70
-
}
71
71
-
}
110
110
+
drawnPaths[path.id].add(segmentPoint);
72
111
}
73
73
-
);
112
112
+
}
113
113
+
}
114
114
+
115
115
+
function drawGrid() {
116
116
+
let color = '#27272a' as unknown as paper.Color;
117
117
+
for (let x = -500; x < 500; x++) {
118
118
+
let topPoint = new paper.Point((x * cellSize) / 5, cellSize * -100);
119
119
+
let bottomPoint = new paper.Point((x * cellSize) / 5, cellSize * 100);
120
120
+
let aLine = new paper.Path.Line(topPoint, bottomPoint);
121
121
+
aLine.strokeColor = color;
122
122
+
aLine.strokeWidth = x % 5 === 0 ? 4 : 1;
123
123
+
124
124
+
let leftPoint = new paper.Point(cellSize * -100, (x * cellSize) / 5);
125
125
+
let rightPoint = new paper.Point(cellSize * 100, (x * cellSize) / 5);
126
126
+
let bLine = new paper.Path.Line(leftPoint, rightPoint);
127
127
+
bLine.strokeColor = color;
128
128
+
bLine.strokeWidth = x % 5 === 0 ? 4 : 1;
129
129
+
}
130
130
+
}
131
131
+
132
132
+
$effect(() => {
133
133
+
if (!gridData[gridId(0, 0)] && painting && painting.cells) {
134
134
+
updatedPosition();
135
135
+
}
74
136
});
75
137
76
76
-
let isDragging = false;
77
77
-
let lastTouch = { x: 0, y: 0 };
78
78
-
let currentPosition = { x: 0, y: 0 };
138
138
+
onDestroy(() => {
139
139
+
for (let id in gridData) {
140
140
+
if (gridData[id]?.unsubscribe) {
141
141
+
gridData[id].unsubscribe();
142
142
+
delete gridData[id];
143
143
+
}
144
144
+
}
145
145
+
});
146
146
+
147
147
+
function getCellBounds() {
148
148
+
let bounds = scope?.view.bounds;
149
149
+
if (!bounds) return;
150
150
+
151
151
+
let topY = Math.floor(bounds.y / cellSize);
152
152
+
let bottomY = Math.floor((bounds.y + bounds.height) / cellSize);
153
153
+
let leftX = Math.floor(bounds.x / cellSize);
154
154
+
let rightX = Math.floor((bounds.x + bounds.width) / cellSize);
155
155
+
156
156
+
return {
157
157
+
top: topY,
158
158
+
bottom: bottomY,
159
159
+
left: leftX,
160
160
+
right: rightX
161
161
+
};
162
162
+
}
163
163
+
164
164
+
function updatedPosition() {
165
165
+
// get visible bounds
166
166
+
let bounds = getCellBounds();
167
167
+
if (!bounds) return;
168
168
+
169
169
+
// console.log('updated position', bounds);
170
170
+
171
171
+
let visibleCells = new Set<string>();
172
172
+
for (let x = bounds.left; x <= bounds.right; x++) {
173
173
+
for (let y = bounds.top; y <= bounds.bottom; y++) {
174
174
+
addSubscription(gridId(x, y));
175
175
+
visibleCells.add(gridId(x, y));
176
176
+
}
177
177
+
}
178
178
+
179
179
+
for (let id in gridData) {
180
180
+
if (!visibleCells.has(id)) {
181
181
+
removeSubscription(id);
182
182
+
}
183
183
+
}
184
184
+
185
185
+
currentX = (scope?.view.center.x ?? 0) / cellSize;
186
186
+
currentY = (scope?.view.center.y ?? 0) / cellSize;
187
187
+
}
188
188
+
189
189
+
function onZoom(delta: number, origin: number[]) {
190
190
+
if (!scope) return;
191
191
+
192
192
+
let newZoom = scope.view.zoom;
193
193
+
let oldZoom = scope.view.zoom;
194
194
+
195
195
+
newZoom = scope.view.zoom + delta;
196
196
+
197
197
+
newZoom = Math.max(0.1, newZoom);
198
198
+
newZoom = Math.min(5, newZoom);
199
199
+
200
200
+
let beta = oldZoom / newZoom;
201
201
+
202
202
+
let mousePosition = new paper.Point(origin[0], origin[1]);
203
203
+
204
204
+
let viewPosition = scope.view.viewToProject(mousePosition);
79
205
80
80
-
// const handleTouchStart = (event: TouchEvent) => {
81
81
-
// if (event.touches.length === 2) {
82
82
-
// // Start dragging, record initial touch points
83
83
-
// isDragging = true;
84
84
-
// lastTouch.x = event.touches[0].pageX - currentPosition.x;
85
85
-
// lastTouch.y = event.touches[0].pageY - currentPosition.y;
86
86
-
// }
87
87
-
// };
206
206
+
let mpos = viewPosition;
207
207
+
let ctr = scope.view.center;
88
208
89
89
-
// const handleTouchMove = (event: TouchEvent) => {
90
90
-
// if (isDragging && event.touches.length === 2) {
91
91
-
// // Calculate the new position based on finger movement
92
92
-
// currentPosition.x = event.touches[0].pageX - lastTouch.x;
93
93
-
// currentPosition.y = event.touches[0].pageY - lastTouch.y;
209
209
+
let pc = mpos.subtract(ctr);
210
210
+
let offset = mpos.subtract(pc.multiply(beta)).subtract(ctr);
94
211
95
95
-
// scope?.view.translate(currentPosition);
96
96
-
// }
97
97
-
// };
212
212
+
scope.view.zoom = newZoom;
213
213
+
scope.view.center = scope.view.center.add(offset);
98
214
99
99
-
// const handleTouchEnd = (event: TouchEvent) => {
100
100
-
// if (event.touches.length < 2) {
101
101
-
// // End dragging when fewer than two fingers are on the screen
102
102
-
// isDragging = false;
103
103
-
// }
104
104
-
// };
215
215
+
updatedPosition();
216
216
+
}
105
217
106
218
onMount(async () => {
107
219
if (!canvas) {
108
220
return;
109
221
}
110
222
223
223
+
setInterval(() => {
224
224
+
updatedPosition();
225
225
+
}, 2000);
226
226
+
111
227
document.addEventListener('gesturestart', (e) => e.preventDefault());
112
228
document.addEventListener('gesturechange', (e) => e.preventDefault());
113
229
114
230
scope = new paper.PaperScope();
115
231
scope.setup(canvas);
116
116
-
tool = new paper.Tool();
232
232
+
drawTool = new paper.Tool();
117
233
118
234
let move = 40;
119
235
window.addEventListener('keydown', (e) => {
···
140
256
}
141
257
});
142
258
143
143
-
let startMatrix: paper.Matrix;
144
144
-
let startMatrixInverted: paper.Matrix;
145
145
-
let p0ProjectCoords: paper.Point;
146
146
-
147
147
-
function getCenterPoint(e: any) {
148
148
-
return new paper.Point(e.origin[0], e.origin[1]);
149
149
-
}
150
150
-
151
259
const gesture = new PinchGesture(canvas, (e) => {
152
260
isOpen = false;
153
153
-
if(!scope)return;
154
154
-
console.log(e);
155
155
-
156
156
-
// if (e.first) {
157
157
-
// startMatrix = scope.view.matrix.clone();
158
158
-
// startMatrixInverted = startMatrix.inverted();
159
159
-
// const p0 = new paper.Point(e.origin[0], e.origin[1]);
160
160
-
// p0ProjectCoords = scope.view.viewToProject(p0);
161
161
-
// } else if (!e.last) {
162
162
-
// const p = new paper.Point(e.origin[0], e.origin[1]);
163
163
-
// const pProject0 = p.transform(startMatrixInverted);
164
164
-
// let currentScale = scope.view.scaling.x ?? 1;
165
165
-
// const delta = pProject0.subtract(p0ProjectCoords).divide(currentScale + e.delta[0]);
166
166
-
// scope.view.matrix = startMatrix.clone().scale(currentScale + e.delta[0], p0ProjectCoords).translate(delta);
167
167
-
// }
261
261
+
if (!scope) return;
262
262
+
onZoom(e.delta[0], e.origin);
168
263
});
169
264
170
170
-
tool.onMouseDown = (event: paper.ToolEvent) => {
171
171
-
isOpen = false;
172
172
-
if (event.modifiers.shift || selectedTool === 'move') {
265
265
+
drawTool.onMouseDown = (event: paper.ToolEvent) => {
266
266
+
if (path) {
267
267
+
finishPath();
173
268
return;
174
269
}
270
270
+
isOpen = false;
175
271
176
272
scope?.activate();
177
273
···
182
278
path.add(event.point);
183
279
};
184
280
185
185
-
tool.onMouseDrag = (event: paper.ToolEvent) => {
281
281
+
drawTool.onMouseDrag = (event: paper.ToolEvent) => {
186
282
isOpen = false;
187
187
-
if (event.modifiers.shift || selectedTool === 'move') {
188
188
-
scope?.view.translate(event.delta);
189
189
-
return;
190
190
-
}
191
283
192
284
if (path === null) {
193
285
return;
···
197
289
path.add(event.point);
198
290
};
199
291
200
200
-
tool.onMouseUp = (event: paper.ToolEvent) => {
201
201
-
if (event.modifiers.shift || path === null) {
202
202
-
return;
203
203
-
}
292
292
+
drawTool.onMouseUp = (event: paper.ToolEvent) => {
293
293
+
finishPath();
294
294
+
};
204
295
205
205
-
scope?.activate();
296
296
+
moveTool = new paper.Tool();
206
297
207
207
-
path.simplify(10);
298
298
+
moveTool.onMouseDrag = function (event: any) {
299
299
+
if (!scope) return;
208
300
209
209
-
const group = Group.create();
210
210
-
group.addMember('everyone', 'writer');
301
301
+
var pan_offset = event.point.subtract(event.downPoint);
302
302
+
scope.view.center = scope.view.center.subtract(pan_offset);
211
303
212
212
-
// add path to painting
213
213
-
let coSegments = Segments.create([], group);
214
214
-
let coPath = Path.create(
215
215
-
{
216
216
-
segments: coSegments,
217
217
-
strokeColor: currentColor,
218
218
-
strokeWidth: strokeWidth
219
219
-
},
220
220
-
group
221
221
-
);
222
222
-
for (let segment of path.segments) {
223
223
-
const position = Point.create(
224
224
-
{
225
225
-
x: segment.point.x,
226
226
-
y: segment.point.y
227
227
-
},
228
228
-
group
229
229
-
);
230
230
-
const handleIn = Point.create(
231
231
-
{
232
232
-
x: segment.handleIn.x,
233
233
-
y: segment.handleIn.y
234
234
-
},
235
235
-
group
236
236
-
);
237
237
-
const handleOut = Point.create(
238
238
-
{
239
239
-
x: segment.handleOut.x,
240
240
-
y: segment.handleOut.y
241
241
-
},
242
242
-
group
243
243
-
);
244
244
-
coSegments.push(
245
245
-
Segment.create(
246
246
-
{
247
247
-
point: position,
248
248
-
handleIn: handleIn,
249
249
-
handleOut: handleOut
250
250
-
},
251
251
-
group
252
252
-
)
253
253
-
);
254
254
-
}
304
304
+
updatedPosition();
305
305
+
};
255
306
256
256
-
drawnPaths[coPath.id] = path;
257
257
-
258
258
-
painting.current?.painting?.push(coPath);
259
259
-
if (painting.current) {
260
260
-
painting.current.count += 1;
261
261
-
}
307
307
+
scope.tool = moveTool;
262
308
263
263
-
path = null;
264
264
-
};
309
309
+
drawGrid();
265
310
});
266
311
267
312
let canvas: HTMLCanvasElement | null = null;
···
277
322
let selectedTool = $state('move');
278
323
279
324
let isOpen = $state(false);
325
325
+
326
326
+
function finishPath() {
327
327
+
if (!path) return;
328
328
+
329
329
+
scope?.activate();
330
330
+
331
331
+
path.simplify(10);
332
332
+
333
333
+
const group = Group.create();
334
334
+
group.addMember('everyone', 'writer');
335
335
+
336
336
+
let coPath = Path.create(
337
337
+
{
338
338
+
segments: path.segments.map((segment) => {
339
339
+
return {
340
340
+
point: {
341
341
+
x: segment.point.x,
342
342
+
y: segment.point.y
343
343
+
},
344
344
+
handleIn: {
345
345
+
x: segment.handleIn.x,
346
346
+
y: segment.handleIn.y
347
347
+
},
348
348
+
handleOut: {
349
349
+
x: segment.handleOut.x,
350
350
+
y: segment.handleOut.y
351
351
+
}
352
352
+
};
353
353
+
}),
354
354
+
strokeColor: currentColor,
355
355
+
strokeWidth: strokeWidth
356
356
+
},
357
357
+
group
358
358
+
);
359
359
+
360
360
+
let gridIdX = Math.floor(path.segments[0].point.x / cellSize);
361
361
+
let gridIdY = Math.floor(path.segments[0].point.y / cellSize);
362
362
+
363
363
+
let id = gridId(gridIdX, gridIdY);
364
364
+
365
365
+
gridData[id] ??= {};
366
366
+
gridData[id].drawnPaths ??= {};
367
367
+
gridData[id].drawnPaths[coPath.id] = path;
368
368
+
369
369
+
if (!painting.cells) return;
370
370
+
371
371
+
if (!painting.cells[id]) {
372
372
+
painting.cells[id] = PaintingCell.create(
373
373
+
{
374
374
+
paths: PaintingPaths.create([], group)
375
375
+
},
376
376
+
group
377
377
+
);
378
378
+
}
379
379
+
painting.cells[id]?.paths?.push(coPath);
380
380
+
381
381
+
path = null;
382
382
+
}
383
383
+
384
384
+
let currentX = $state(0.0001);
385
385
+
let currentY = $state(0.0001);
280
386
</script>
281
387
282
388
<div class="fixed right-2 bottom-2 z-20 flex flex-col gap-3"></div>
···
287
393
'fixed h-screen w-screen',
288
394
selectedTool === 'move' ? 'cursor-grab' : 'cursor-crosshair'
289
395
)}
396
396
+
data-paper-resize="true"
290
397
></canvas>
291
398
292
292
-
<!-- ontouchstart={handleTouchStart}
293
293
-
ontouchmove={handleTouchMove}
294
294
-
ontouchend={handleTouchEnd} -->
295
295
-
296
296
-
<div class="fixed top-2 left-2 z-20">
399
399
+
<div class="fixed top-2 left-2 z-20 hidden">
297
400
<Button
298
401
onclick={() => {
299
299
-
// toast('Link copied to clipboard', {
300
300
-
// description: 'Send this link to your friends to draw together'
301
301
-
// });
302
302
-
// navigator.clipboard.writeText(window.location.href);
402
402
+
const gridIds = Object.keys(gridData);
403
403
+
const randomGridId = gridIds[Math.floor(Math.random() * gridIds.length)];
404
404
+
405
405
+
if (!gridData[randomGridId]) return;
406
406
+
407
407
+
let drawnPaths = gridData[randomGridId].drawnPaths;
408
408
+
if (!drawnPaths) return;
303
409
304
410
// jump to random drawing
305
411
const numDrawnPaths = Object.keys(drawnPaths).length;
···
311
417
// scope?.view.center.set(new paper.Point(point.x, point.y));
312
418
// get current translation
313
419
const translation = scope?.view.center;
314
314
-
console.log(translation);
420
420
+
// console.log(translation);
315
421
if (translation) {
316
422
scope?.view.translate(new paper.Point(translation.x - point.x, translation.y - point.y));
317
423
···
323
429
>
324
430
Random point
325
431
</Button>
432
432
+
433
433
+
<Button
434
434
+
onclick={() => {
435
435
+
// add 100 random paths
436
436
+
let paths = [];
437
437
+
for (let i = 0; i < 100000; i++) {
438
438
+
const path = new paper.Path();
439
439
+
path.strokeColor = currentColor as unknown as paper.Color;
440
440
+
path.strokeWidth = strokeWidth;
441
441
+
let rx = Math.random() * 10000 - 5000;
442
442
+
let ry = Math.random() * 10000 - 5000;
443
443
+
path.add(new paper.Point(rx, ry));
444
444
+
path.add(new paper.Point(rx + 50, ry + 50));
445
445
+
paths.push(path);
446
446
+
447
447
+
continue;
448
448
+
449
449
+
const group = Group.create();
450
450
+
group.addMember('everyone', 'writer');
451
451
+
452
452
+
let coPath = Path.create(
453
453
+
{
454
454
+
segments: path.segments.map((segment) => {
455
455
+
return {
456
456
+
point: {
457
457
+
x: segment.point.x,
458
458
+
y: segment.point.y
459
459
+
},
460
460
+
handleIn: {
461
461
+
x: segment.handleIn.x,
462
462
+
y: segment.handleIn.y
463
463
+
},
464
464
+
handleOut: {
465
465
+
x: segment.handleOut.x,
466
466
+
y: segment.handleOut.y
467
467
+
}
468
468
+
};
469
469
+
}),
470
470
+
strokeColor: currentColor,
471
471
+
strokeWidth: strokeWidth
472
472
+
},
473
473
+
group
474
474
+
);
475
475
+
476
476
+
painting?.paths?.push(coPath);
477
477
+
}
478
478
+
479
479
+
//create new group
480
480
+
const pGroup = new paper.Group(paths);
481
481
+
}}
482
482
+
>
483
483
+
Add lots
484
484
+
</Button>
326
485
</div>
327
486
328
328
-
<div class="absolute top-2 right-2">
487
487
+
<div class="fixed top-2 left-2 z-20 p-0">
488
488
+
<Box class="px-1 py-1">
489
489
+
<div class="bg-base-800/50 rounded-2xl px-2">
490
490
+
<NumberFlow
491
491
+
value={currentX}
492
492
+
format={{ minimumFractionDigits: 1, maximumFractionDigits: 1 }}
493
493
+
/> /
494
494
+
<NumberFlow
495
495
+
value={currentY}
496
496
+
format={{ minimumFractionDigits: 1, maximumFractionDigits: 1 }}
497
497
+
/>
498
498
+
</div>
499
499
+
</Box>
500
500
+
</div>
501
501
+
{#if !painting || !loaded}
502
502
+
<div class="pointer-events-none fixed inset-0 flex h-full w-full items-center justify-center">
503
503
+
<Subheading>Loading...</Subheading>
504
504
+
</div>
505
505
+
{/if}
506
506
+
507
507
+
<div class="absolute right-2 bottom-2">
329
508
<div class="bg-base-900 flex items-center justify-center gap-4 rounded-2xl p-2">
330
509
<ToggleGroup
331
510
type="single"
···
334
513
return selectedTool;
335
514
},
336
515
(value) => {
516
516
+
if (!value) return;
517
517
+
337
518
selectedTool = value;
519
519
+
if (!scope) return;
520
520
+
if (selectedTool === 'move') {
521
521
+
scope.tool = moveTool!;
522
522
+
} else {
523
523
+
scope.tool = drawTool!;
524
524
+
}
338
525
}
339
526
}
340
527
>
···
403
590
<ColorPicker
404
591
bind:rgb={color}
405
592
onchange={(color) => {
406
406
-
console.log(color);
407
593
currentColor = color.hex;
408
594
}}
409
595
quickSelects={[