Compare commits

..

22 Commits

Author SHA1 Message Date
Guido D'Orsi
ceaa555e83 Merge pull request #2727 from garden-co/changeset-release/main
Version Packages
2025-08-14 11:49:33 +02:00
github-actions[bot]
03229b2ea9 Version Packages 2025-08-14 08:51:24 +00:00
Guido D'Orsi
e2737d44b6 fix: add jazz-run as workspace dependency 2025-08-14 10:48:02 +02:00
Guido D'Orsi
4b73834883 chore: changeset 2025-08-14 10:47:44 +02:00
Guido D'Orsi
1b3d43d5f4 Merge pull request #2728 from legowhales/main
fix(jazz-tools/svelte): Make Image reactive to imageId change
2025-08-14 10:46:52 +02:00
Guido D'Orsi
9c9a689879 Merge pull request #2729 from garden-co/feat/debug-correction
feat: add debug info to correction errors
2025-08-13 17:43:53 +02:00
Guido D'Orsi
2fd88b938c feat: add debug info to correction errors 2025-08-13 12:33:05 +02:00
Sammii
d1f955006f Merge pull request #2702 from garden-co/feat/add-input-label
Feat/add input label
2025-08-13 10:33:51 +01:00
Jérémy Le Mardelé
bb3d5f1f87 fix(jazz-tools/svelte): Make Image reactive to imageId change 2025-08-13 00:42:33 +02:00
Sammii
26ce61ab78 input PR amends 2025-08-12 15:59:30 +01:00
Sammii
1f300114d5 label PR amends 2025-08-12 15:58:42 +01:00
Guido D'Orsi
da69f812f8 Merge pull request #2726 from garden-co/fix/GCO-726
fix(jazz-tools/media): ensure file downloaded in loadImageBySize
2025-08-12 14:30:28 +02:00
Guido D'Orsi
0bcbf551ca fix: export the HttpRoute type 2025-08-12 14:26:31 +02:00
Matteo Manchi
6b3d5b5560 fixup! fix(jazz-tools/media): ensure file downloaded in loadImageBySize 2025-08-12 11:44:54 +02:00
Matteo Manchi
d1bdbf5d49 fix(jazz-tools/media): ensure file downloaded in loadImageBySize 2025-08-12 10:37:34 +02:00
Sammii
9b22fc74cd ignore biome on label cn import 2025-08-11 11:19:49 +01:00
Sammii
1bebe3c6c8 Merge branch 'main' into feat/add-input-label 2025-08-11 11:00:05 +01:00
Sammii
e1bd16d08b amend cn import on label 2025-08-11 10:58:47 +01:00
Sammii
0967c2ee5a pr amends 2025-08-11 10:49:34 +01:00
Sammii
72b5542130 Merge branch 'main' into feat/add-input-label 2025-08-05 12:41:06 +01:00
Sammii
5fd9225a54 adding intent styles to input + u0pdating docs 2025-08-05 12:32:52 +01:00
Sammii
9138d30208 create input and label components, with docs 2025-08-05 12:18:49 +01:00
45 changed files with 492 additions and 53 deletions

View File

@@ -1,5 +1,14 @@
# passkey-svelte
## 0.0.114
### Patch Changes
- Updated dependencies [0bcbf55]
- Updated dependencies [d1bdbf5]
- Updated dependencies [4b73834]
- jazz-tools@0.17.1
## 0.0.113
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-svelte",
"version": "0.0.113",
"version": "0.0.114",
"type": "module",
"private": true,
"scripts": {

View File

@@ -17,6 +17,7 @@
"@vitest/coverage-v8": "catalog:",
"@vitest/ui": "catalog:",
"happy-dom": "^17.4.4",
"jazz-run": "workspace:*",
"jazz-tools": "workspace:*",
"lefthook": "^1.8.2",
"pkg-pr-new": "^0.0.39",

View File

@@ -1,5 +1,12 @@
# cojson-storage-indexeddb
## 0.17.1
### Patch Changes
- Updated dependencies [2fd88b9]
- cojson@0.17.1
## 0.17.0
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.17.0",
"version": "0.17.1",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,12 @@
# cojson-storage-sqlite
## 0.17.1
### Patch Changes
- Updated dependencies [2fd88b9]
- cojson@0.17.1
## 0.17.0
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.17.0",
"version": "0.17.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,5 +1,12 @@
# cojson-transport-nodejs-ws
## 0.17.1
### Patch Changes
- Updated dependencies [2fd88b9]
- cojson@0.17.1
## 0.17.0
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.17.0",
"version": "0.17.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,5 +1,11 @@
# cojson
## 0.17.1
### Patch Changes
- 2fd88b9: Add debug info to sync correction errors
## 0.17.0
## 0.16.6

View File

@@ -25,7 +25,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.17.0",
"version": "0.17.1",
"devDependencies": {
"@opentelemetry/sdk-metrics": "^2.0.0",
"libsql": "^0.5.13",

View File

@@ -84,3 +84,10 @@ export function getContentMessageSize(msg: NewContentMessage) {
);
}, 0);
}
export function getContenDebugInfo(msg: NewContentMessage) {
return Object.entries(msg.new).map(
([sessionID, sessionNewContent]) =>
`Session: ${sessionID} After: ${sessionNewContent.after} New: ${sessionNewContent.newTransactions.length}`,
);
}

View File

@@ -208,7 +208,6 @@ export class StorageApiSync implements StorageAPI {
if (!correction) {
logger.error("Correction callback returned undefined", {
knownState,
correction: correction ?? null,
});
return false;
}

View File

@@ -2,6 +2,7 @@ import { Histogram, ValueType, metrics } from "@opentelemetry/api";
import { PeerState } from "./PeerState.js";
import { SyncStateManager } from "./SyncStateManager.js";
import {
getContenDebugInfo,
getTransactionSize,
knownStateFromContent,
} from "./coValueContentMessage.js";
@@ -673,6 +674,8 @@ export class SyncManager {
"Invalid state assumed when handling new content from storage",
{
id: msg.id,
content: getContenDebugInfo(msg),
knownState: coValue.knownState(),
},
);
}
@@ -803,7 +806,20 @@ export class SyncManager {
// Try to store the content as-is for performance
// In case that some transactions are missing, a correction will be requested, but it's an edge case
storage.store(content, (correction) => {
return value.verified?.newContentSince(correction);
if (!value.verified) {
logger.error(
"Correction requested for a CoValue with no verified content",
{
id: content.id,
content: getContenDebugInfo(content),
correction,
state: value.loadingState,
},
);
return undefined;
}
return value.verified.newContentSince(correction);
});
}

View File

@@ -382,7 +382,6 @@ describe("StorageApiSync", () => {
"Correction callback returned undefined",
{
knownState: expect.any(Object),
correction: null,
},
);
@@ -413,7 +412,6 @@ describe("StorageApiSync", () => {
"Correction callback returned undefined",
{
knownState: expect.any(Object),
correction: null,
},
);

View File

@@ -1,5 +1,17 @@
# jazz-auth-betterauth
## 0.17.1
### Patch Changes
- Updated dependencies [0bcbf55]
- Updated dependencies [2fd88b9]
- Updated dependencies [d1bdbf5]
- Updated dependencies [4b73834]
- jazz-tools@0.17.1
- cojson@0.17.1
- jazz-betterauth-client-plugin@0.17.1
## 0.17.0
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-auth-betterauth",
"version": "0.17.0",
"version": "0.17.1",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,11 @@
# jazz-betterauth-client-plugin
## 0.17.1
### Patch Changes
- jazz-betterauth-server-plugin@0.17.1
## 0.17.0
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-client-plugin",
"version": "0.17.0",
"version": "0.17.1",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,16 @@
# jazz-betterauth-server-plugin
## 0.17.1
### Patch Changes
- Updated dependencies [0bcbf55]
- Updated dependencies [2fd88b9]
- Updated dependencies [d1bdbf5]
- Updated dependencies [4b73834]
- jazz-tools@0.17.1
- cojson@0.17.1
## 0.17.0
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-server-plugin",
"version": "0.17.0",
"version": "0.17.1",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,18 @@
# jazz-react-auth-betterauth
## 0.17.1
### Patch Changes
- Updated dependencies [0bcbf55]
- Updated dependencies [2fd88b9]
- Updated dependencies [d1bdbf5]
- Updated dependencies [4b73834]
- jazz-tools@0.17.1
- cojson@0.17.1
- jazz-auth-betterauth@0.17.1
- jazz-betterauth-client-plugin@0.17.1
## 0.17.0
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-auth-betterauth",
"version": "0.17.0",
"version": "0.17.1",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",

View File

@@ -1,5 +1,18 @@
# jazz-run
## 0.17.1
### Patch Changes
- Updated dependencies [0bcbf55]
- Updated dependencies [2fd88b9]
- Updated dependencies [d1bdbf5]
- Updated dependencies [4b73834]
- jazz-tools@0.17.1
- cojson@0.17.1
- cojson-storage-sqlite@0.17.1
- cojson-transport-ws@0.17.1
## 0.17.0
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.17.0",
"version": "0.17.1",
"exports": {
"./startSyncServer": {
"types": "./dist/startSyncServer.d.ts",
@@ -28,11 +28,11 @@
"@effect/printer-ansi": "^0.34.5",
"@effect/schema": "^0.71.1",
"@effect/typeclass": "^0.25.5",
"cojson": "workspace:0.17.0",
"cojson-storage-sqlite": "workspace:0.17.0",
"cojson-transport-ws": "workspace:0.17.0",
"cojson": "workspace:0.17.1",
"cojson-storage-sqlite": "workspace:0.17.1",
"cojson-transport-ws": "workspace:0.17.1",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.17.0",
"jazz-tools": "workspace:0.17.1",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,5 +1,17 @@
# jazz-tools
## 0.17.1
### Patch Changes
- 0bcbf55: Export the HttpRoute type
- d1bdbf5: fix: ensure file downloaded in loadImageBySize
- 4b73834: fix(jazz-tools/svelte): Make Image reactive to imageId change
- Updated dependencies [2fd88b9]
- cojson@0.17.1
- cojson-storage-indexeddb@0.17.1
- cojson-transport-ws@0.17.1
## 0.17.0
### Minor Changes

View File

@@ -140,7 +140,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.17.0",
"version": "0.17.1",
"dependencies": {
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
"@scure/base": "1.2.1",

View File

@@ -1,7 +1,11 @@
import { Account, FileStream, ImageDefinition } from "jazz-tools";
import { highestResAvailable, loadImageBySize } from "jazz-tools/media";
import { createJazzTestAccount, setupJazzTestSync } from "jazz-tools/testing";
import { Account, FileStream, Group, ImageDefinition } from "jazz-tools";
import {
createJazzTestAccount,
setActiveAccount,
setupJazzTestSync,
} from "jazz-tools/testing";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { highestResAvailable, loadImageBySize } from "./utils.js";
const createFileStream = (account: any, blobSize?: number) => {
return FileStream.createFromBlob(
@@ -209,23 +213,21 @@ describe("highestResAvailable", async () => {
describe("loadImageBySize", async () => {
let account: Account;
beforeEach(async () => {
account = await createJazzTestAccount({
isCurrentActiveAccount: true,
});
vi.spyOn(Account, "getMe").mockReturnValue(account);
await setupJazzTestSync();
account = await setupJazzTestSync();
setActiveAccount(account);
});
const createImageDef = async (
sizes: Array<[number, number]>,
progressive = true,
owner: Account | Group = account,
) => {
if (sizes.length === 0) throw new Error("sizes array must not be empty");
const originalSize = sizes[sizes.length - 1]!;
sizes = sizes.slice(0, -1);
const original = await createFileStream(account, 1);
const original = await createFileStream(owner, 1);
// Ensure sizes array is not empty
const imageDef = ImageDefinition.create(
{
@@ -233,14 +235,14 @@ describe("loadImageBySize", async () => {
progressive,
original,
},
{ owner: account },
{ owner },
);
imageDef[`${originalSize[0]}x${originalSize[1]}`] = original;
for (const size of sizes) {
if (!size) continue;
const [w, h] = size;
imageDef[`${w}x${h}`] = await createFileStream(account, 1);
imageDef[`${w}x${h}`] = await createFileStream(owner, 1);
}
return imageDef;
};
@@ -251,6 +253,24 @@ describe("loadImageBySize", async () => {
expect(result?.image.id).toBe(imageDef["1920x1080"]!.id);
});
it("returns the original image already loaded", async () => {
const account = await setupJazzTestSync({ asyncPeers: true });
const account2 = await createJazzTestAccount();
setActiveAccount(account);
const group = Group.create();
group.addMember("everyone", "reader");
const imageDef = await createImageDef([[1920, 1080]], false, group);
setActiveAccount(account2);
const result = await loadImageBySize(imageDef, 256, 256);
expect(result?.image.id).toBe(imageDef["1920x1080"]!.id);
expect(result?.image.isBinaryStreamEnded()).toBe(true);
expect(result?.image.asBase64()).toStrictEqual(expect.any(String));
});
it("returns null if no sizes are available", async () => {
const original = await createFileStream(account._owner, 1);
const imageDef = ImageDefinition.create(
@@ -324,4 +344,30 @@ describe("loadImageBySize", async () => {
expect(result?.width).toBe(1024);
expect(result?.height).toBe(1024);
});
it("returns the image already loaded", async () => {
const account = await setupJazzTestSync({ asyncPeers: true });
const account2 = await createJazzTestAccount();
setActiveAccount(account);
const group = Group.create();
group.addMember("everyone", "reader");
const imageDef = await createImageDef(
[
[512, 512],
[1024, 1024],
],
undefined,
group,
);
setActiveAccount(account2);
const result = await loadImageBySize(imageDef, 1024, 1024);
expect(result?.image.id).toBe(imageDef["1024x1024"]!.id);
expect(result?.image.isBinaryStreamEnded()).toBe(true);
expect(result?.image.asBase64()).toStrictEqual(expect.any(String));
});
});

View File

@@ -126,21 +126,22 @@ export async function loadImage(
};
}
await imageOrId.ensureLoaded({
resolve: {
original: true,
},
});
if (!imageOrId.original) {
console.warn("Unable to find the original image");
return null;
}
const loadedOriginal = await FileStream.load(imageOrId.original.id);
if (!loadedOriginal) {
console.warn("Unable to find the original image");
return null;
}
return {
width: imageOrId.originalSize[0],
height: imageOrId.originalSize[1],
image: imageOrId.original,
image: loadedOriginal,
};
}
@@ -149,7 +150,7 @@ export async function loadImageBySize(
wantedWidth: number,
wantedHeight: number,
): Promise<{ width: number; height: number; image: FileStream } | null> {
const image =
const image: ImageDefinition | null =
typeof imageOrId === "string"
? await ImageDefinition.load(imageOrId)
: imageOrId;
@@ -184,19 +185,21 @@ export async function loadImageBySize(
const bestTarget =
sortedSizes.find((el) => el.match > 0.95) || sortedSizes.at(-1)!;
const deepLoaded = await ImageDefinition.load(image.id, {
resolve: {
[bestTarget.size[2]]: true,
},
});
const file = image[bestTarget.size[2]];
if (deepLoaded === null || deepLoaded[bestTarget.size[2]] === undefined) {
if (!file) {
return null;
}
const loadedFile = await FileStream.load(file.id);
if (!loadedFile) {
return null;
}
return {
width: bestTarget.size[0],
height: bestTarget.size[1],
image: deepLoaded[bestTarget.size[2]]!,
image: loadedFile,
};
}

View File

@@ -13,7 +13,7 @@ interface ImageProps extends Omit<HTMLImgAttributes, "width" | "height"> {
const { imageId, width, height, ...rest }: ImageProps = $props();
const imageState = new CoState(ImageDefinition, imageId);
const imageState = new CoState(ImageDefinition, () => imageId);
let lastBestImage: [string, string] | null = null;
/**

View File

@@ -330,7 +330,7 @@ function parseSchemaAndResolve<
};
}
class HttpRoute<
export class HttpRoute<
RequestShape extends MessageShape = z.core.$ZodLooseShape,
RequestResolve extends ResolveQuery<CoMapSchema<RequestShape>> = any,
ResponseShape extends MessageShape = z.core.$ZodLooseShape,

View File

@@ -113,4 +113,5 @@ export {
experimental_defineRequest,
JazzRequestError,
isJazzRequestError,
type HttpRoute,
} from "./coValues/request.js";

View File

@@ -0,0 +1,63 @@
import Input from "@/src/components/input";
import Label from "@/src/components/label";
import { SearchIcon } from "lucide-react";
export default function InputPage() {
return (
<div className="flex flex-col gap-4">
<h2 className="text-2xl mb-2 font-bold">Input</h2>
<p className="mb-3">
Inputs are used in conjunction with a label and can be styled with the
intent and size props.
</p>
<div className="flex flex-row gap-2">
<Label htmlFor="input">Label</Label>
<Input id="input" />
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="input">Label</Label>
<Input id="input" intent="primary" />
</div>
<div className="flex flex-row gap-2">
<Label htmlFor="input" size="sm">
Label
</Label>
<Input id="input" intent="tip" sizeStyle="sm" />
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="input" size="sm">
Label
</Label>
<Input id="input" intent="info" sizeStyle="sm" />
</div>
<div className="flex flex-row gap-2">
<Label htmlFor="input" size="lg">
Label
</Label>
<Input id="input" intent="warning" sizeStyle="lg" />
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="input" size="lg">
Label
</Label>
<Input id="input" intent="danger" sizeStyle="lg" />
</div>
<p>
Labels should alway be used with an input, but can be hidden with the
isHiddenVisually prop.
</p>
<div className="flex flex-row gap-2 items-center">
<Label htmlFor="input" isHiddenVisually>
Label
</Label>
<SearchIcon />
<Input id="input" />
</div>
</div>
);
}

View File

@@ -8,6 +8,7 @@ export default function DocsPage() {
<li>
<Link href="/docs/button">Button</Link>
<Link href="/docs/icon">Icon</Link>
<Link href="/docs/input">Input</Link>
</li>
</ul>
</div>

View File

@@ -0,0 +1,39 @@
import { Input as BaseUiInput } from "@base-ui-components/react/input";
import * as React from "react";
import { ComponentProps } from "react";
import { VariantProps, tv } from "tailwind-variants";
type InputVariants = VariantProps<typeof input>;
interface InputProps extends ComponentProps<"input">, InputVariants {}
export default function Input({ sizeStyle, intent, ...props }: InputProps) {
return <BaseUiInput className={input({ sizeStyle, intent })} {...props} />;
}
const input = tv({
base: "w-full rounded-md border pl-3.5 text-base text-gray-900",
variants: {
base: "w-full rounded-md border px-2.5 py-1 shadow-sm h-[36px] font-medium text-stone-900 dark:text-white dark:bg-stone-925",
intent: {
default: "border-stone-500/50 focus:ring-stone-800/50",
primary: "border-primary focus:ring-blue/50",
success: "border-success focus:ring-green/50",
warning: "border-warning focus:ring-yellow/50",
danger: "border-danger focus:ring-red/50",
info: "border-info focus:ring-blue/50",
tip: "border-tip focus:ring-cyan/50",
muted: "border-muted focus:ring-gray/50",
strong: "border-strong focus:ring-stone-900/50",
},
sizeStyle: {
sm: "text-sm py-1 px-2 [&>svg]:size-4 h-7",
md: "py-1.5 px-3 h-[36px] [&>svg]:size-5 h-9",
lg: "py-2 px-5 md:px-6 md:py-2.5 [&>svg]:size-6 h-10",
},
},
defaultVariants: {
sizeStyle: "md",
intent: "default",
},
});

View File

@@ -0,0 +1,45 @@
import { ComponentProps } from "react";
import { VariantProps, tv } from "tailwind-variants";
// biome-ignore lint/correctness/useImportExtensions: <explanation>
import { cn } from "../lib/utils";
type LabelVariants = VariantProps<typeof label>;
interface LabelProps extends ComponentProps<"label">, LabelVariants {}
export default function Label({
size,
isHiddenVisually,
...props
}: LabelProps) {
return (
<label
className={cn(
label({
isHiddenVisually: isHiddenVisually,
size: size,
}),
props.className,
)}
{...props}
/>
);
}
const label = tv({
base: "block text-sm font-medium text-stone-900 dark:text-white flex items-center",
variants: {
isHiddenVisually: {
true: "sr-only",
},
size: {
sm: "text-sm",
md: "text-base",
lg: "text-lg",
},
},
defaultVariants: {
size: "md",
isHiddenVisually: false,
},
});

11
pnpm-lock.yaml generated
View File

@@ -67,6 +67,9 @@ importers:
happy-dom:
specifier: ^17.4.4
version: 17.4.4
jazz-run:
specifier: workspace:*
version: link:packages/jazz-run
jazz-tools:
specifier: workspace:*
version: link:packages/jazz-tools
@@ -1851,19 +1854,19 @@ importers:
specifier: ^0.25.5
version: 0.25.8(effect@3.11.9)
cojson:
specifier: workspace:0.17.0
specifier: workspace:0.17.1
version: link:../cojson
cojson-storage-sqlite:
specifier: workspace:0.17.0
specifier: workspace:0.17.1
version: link:../cojson-storage-sqlite
cojson-transport-ws:
specifier: workspace:0.17.0
specifier: workspace:0.17.1
version: link:../cojson-transport-ws
effect:
specifier: ^3.6.5
version: 3.11.9
jazz-tools:
specifier: workspace:0.17.0
specifier: workspace:0.17.1
version: link:../jazz-tools
ws:
specifier: ^8.14.2

View File

@@ -1,5 +1,14 @@
# jazz-react-tailwind-starter
## 0.0.145
### Patch Changes
- Updated dependencies [0bcbf55]
- Updated dependencies [d1bdbf5]
- Updated dependencies [4b73834]
- jazz-tools@0.17.1
## 0.0.144
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-react-passkey-auth-starter",
"private": true,
"version": "0.0.144",
"version": "0.0.145",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,14 @@
# svelte-passkey-auth
## 0.0.119
### Patch Changes
- Updated dependencies [0bcbf55]
- Updated dependencies [d1bdbf5]
- Updated dependencies [4b73834]
- jazz-tools@0.17.1
## 0.0.118
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "svelte-passkey-auth",
"version": "0.0.118",
"version": "0.0.119",
"type": "module",
"private": true,
"scripts": {

View File

@@ -1,2 +1,3 @@
<a href="/costate">CoState</a>
<a href="/media">Media</a>
<a href="/virtual-list">Virtual List</a>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import { JazzSvelteProvider } from 'jazz-tools/svelte';
import "jazz-tools/inspector/register-custom-element"
import { TestAccount } from './schema.js';
if (typeof window !== 'undefined') {
localStorage.clear(); // Want to start always from a fresh account
}
let { children } = $props();
</script>
<JazzSvelteProvider
AccountSchema={TestAccount}
sync={{
when: "never"
}}
>
<jazz-inspector></jazz-inspector>
{@render children()}
</JazzSvelteProvider>

View File

@@ -0,0 +1,63 @@
<script lang="ts">
import type { ChangeEventHandler } from 'svelte/elements';
import { AccountCoState, Image } from 'jazz-tools/svelte';
import { createImage } from 'jazz-tools/media';
import { TestAccount } from './schema.js';
const me = new AccountCoState(TestAccount, {
resolve: {
profile: {
image: true
}
}
});
let input = $state<HTMLInputElement>();
const onUploadClick = () => {
input?.click();
};
const onImageChange: ChangeEventHandler<HTMLInputElement> = (event) => {
const file = event.currentTarget.files?.[0];
if (!file || !me.current?.profile) return;
createImage(file, {
owner: me.current?.profile._owner,
maxSize: 400
}).then((image) => {
if (!me.current?.profile) return;
me.current.profile.image = image;
});
}
</script>
<button onclick={onUploadClick}>
{me.current?.profile?.image ? 'Change image' : 'Send image'}
</button>
{#if me.current?.profile?.image}
<Image imageId={me.current.profile.image.id} width={200} height="original" />
{/if}
<label>
<input
bind:this={input}
type="file"
accept="image/png, image/jpeg, image/gif"
onchange={onImageChange}
/>
</label>
<style>
label {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
</style>

View File

@@ -0,0 +1,10 @@
import { co } from "jazz-tools";
export const Profile = co.profile({
image: co.optional(co.image()),
});
export const TestAccount = co.account({
profile: Profile,
root: co.map({}),
});