Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d32cc49
first version of merged query
nikgraf Feb 6, 2025
4b163dd
refactor useQuery
nikgraf Mar 18, 2025
2338269
add spinner and improve loading experience
nikgraf Mar 18, 2025
efe0872
fix test
nikgraf Mar 18, 2025
44ec55b
filter out entities marked as deleted from local result
nikgraf Mar 18, 2025
01fc533
fix linting
nikgraf Mar 18, 2025
bbf9227
fix loading experience
nikgraf Mar 19, 2025
2158f57
fix login behaviour by reseting first and then setting the auth
nikgraf Mar 19, 2025
a86843c
fix types
nikgraf Mar 19, 2025
3082921
fix delete by using correct endpoint
nikgraf Mar 19, 2025
bb853a7
invalidate queries after publishing
nikgraf Mar 19, 2025
b16b4eb
fix bug filtering out deleted entities
nikgraf Mar 19, 2025
0baa851
fix returning multiple entities
nikgraf Mar 19, 2025
6861e89
show deleted local todos
nikgraf Mar 19, 2025
62ccd8c
fix update
nikgraf Mar 20, 2025
133e005
add prepare publish functionality
nikgraf Mar 23, 2025
7c75d65
swtich from kg to geo for deleting entities
nikgraf Mar 24, 2025
d31aacd
restructure utilities
nikgraf Mar 24, 2025
df8ccd0
expose more information in getDiff
nikgraf Mar 24, 2025
413ce3b
fix checked state for public queries
nikgraf Mar 24, 2025
ff3b874
create PublishDiff component
nikgraf Mar 24, 2025
2ee0ba1
add loading states to buttons
nikgraf Mar 24, 2025
3d87051
add type information
nikgraf Mar 24, 2025
983b992
ignore updates in the __version field
nikgraf Mar 24, 2025
d1d9cdf
hotfix the entity type name for the publish diff component
nikgraf Mar 24, 2025
d5aa216
only require mapping if in merged mode
nikgraf Mar 25, 2025
1cdc4de
split up todos2
nikgraf Mar 25, 2025
e2399b1
add user mapping
nikgraf Mar 25, 2025
04e68a6
refactor and cleanup diff structure
nikgraf Mar 25, 2025
9faf6d2
fix types to pass different entities to the PublishDiff
nikgraf Mar 25, 2025
6706d83
render relation from public query
nikgraf Mar 30, 2025
fd76a0b
add users tab
nikgraf Mar 30, 2025
3f71c47
ensure stable references to the data array for use-query
nikgraf Mar 31, 2025
6f4a404
add assignee selector
nikgraf Mar 31, 2025
c8c237e
improve relation creation
nikgraf Mar 31, 2025
c2fe954
fix loading entities when switching tabs
nikgraf Mar 31, 2025
be0fa3c
improve diff for relations
nikgraf Apr 1, 2025
5c18295
fix relation setup for public integration and implement generating up…
nikgraf Apr 2, 2025
5fac44a
allow to remove assignees again
nikgraf Apr 2, 2025
c32fb72
fix showing multiple relations in the public query
nikgraf Apr 2, 2025
1108f08
use separate relation entries instead of adding it to the entity
nikgraf Apr 9, 2025
3af2031
fix updating the list after changing a related entity
nikgraf Apr 11, 2025
be9bd6b
add relationParentsMap
nikgraf Apr 13, 2025
230ec7b
refactor smartaccount instantiation
nikgraf Apr 13, 2025
bb57575
introduce filters
nikgraf Apr 13, 2025
4a7d692
provide relationId from local relation when publishing
nikgraf Apr 14, 2025
6e4347c
cleanup app structure
nikgraf Apr 14, 2025
563d29d
update mapping
nikgraf Apr 17, 2025
619cad8
fix md5 hashing for web platform
nikgraf Apr 22, 2025
394ac72
fix tests
nikgraf Apr 22, 2025
59b3ee7
update effect packages
nikgraf Apr 22, 2025
f085e63
wip patching the entity types
nikgraf Apr 24, 2025
6425aec
improve types
nikgraf Apr 25, 2025
1be3201
cleanup types
nikgraf Apr 28, 2025
59a7388
implement include for local & public queries
nikgraf Apr 28, 2025
2c4bda7
add more filters
nikgraf Apr 29, 2025
5a2a3f5
enhance filter capabilities
nikgraf Apr 29, 2025
1d5d2c5
Merge branch 'main' into ng/public-graph-integration
nikgraf May 2, 2025
e0c822b
fix import
nikgraf May 2, 2025
5910d3b
remove unused filter-types
nikgraf May 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@
"@automerge/automerge": "^v2.2.9-alpha.3",
"@automerge/automerge-repo": "^2.0.0-alpha.14",
"@automerge/automerge-repo-react-hooks": "^2.0.0-alpha.14",
"@graphprotocol/grc-20": "^0.10.0",
"@graphprotocol/hypergraph": "workspace:*",
"@graphprotocol/hypergraph-react": "workspace:*",
"@noble/hashes": "^1.7.0",
"@privy-io/react-auth": "^2.0.3",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-slot": "^1.1.1",
"@tanstack/react-query": "^5.67.1",
"@tanstack/react-router": "^1.97.1",
"@xstate/store": "^2.6.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"effect": "^3.12.4",
"effect": "^3.14.12",
"framer-motion": "^12.5.0",
"graphql-request": "^7.1.2",
"isomorphic-ws": "^5.0.0",
"lucide-react": "^0.471.1",
"react": "^19.0.0",
Expand Down
89 changes: 89 additions & 0 deletions apps/events/src/components/create-properties-and-types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { getSmartAccountWalletClient } from '@/lib/smart-account';
import { type GeoSmartAccount, Graph, type Op } from '@graphprotocol/grc-20';
import { publishOps, useHypergraphSpace } from '@graphprotocol/hypergraph-react';
import { useState } from 'react';
import { Button } from './ui/button';
import { Card, CardContent } from './ui/card';

const createPropertiesAndTypes = async ({
smartAccountWalletClient,
space,
}: { smartAccountWalletClient: GeoSmartAccount; space: string }) => {
const ops: Array<Op> = [];
const { id: checkedPropertyId, ops: createCheckedPropertyOps } = Graph.createProperty({
type: 'CHECKBOX',
name: 'Checked',
});
ops.push(...createCheckedPropertyOps);

const { id: userId, ops: createUserOps } = Graph.createType({
name: 'User',
});
ops.push(...createUserOps);

const { id: assigneesRelationTypeId, ops: createAssigneesRelationTypeOps } = Graph.createProperty({
type: 'RELATION',
name: 'Assignees',
relationValueTypes: [userId],
});
ops.push(...createAssigneesRelationTypeOps);

const { id: todoTypeId, ops: createTodoTypeOps } = Graph.createType({
name: 'Todo',
properties: [checkedPropertyId, assigneesRelationTypeId],
});
ops.push(...createTodoTypeOps);

const result = await publishOps({ ops, walletClient: smartAccountWalletClient, space });
return { result, todoTypeId, checkedPropertyId, userId, assigneesRelationTypeId };
};

export const CreatePropertiesAndTypes = () => {
const [mapping, setMapping] = useState<string>('');
const space = useHypergraphSpace();

return (
<div>
{mapping && (
<Card>
<CardContent>
<pre>{mapping}</pre>
</CardContent>
</Card>
)}
<Button
onClick={async () => {
const smartAccountWalletClient = await getSmartAccountWalletClient();
if (!smartAccountWalletClient) {
throw new Error('Missing smartAccountWalletClient');
}
const { todoTypeId, checkedPropertyId, userId, assigneesRelationTypeId } = await createPropertiesAndTypes({
smartAccountWalletClient,
space,
});

const newMapping = `Todo2: {
typeIds: [Id.Id('${todoTypeId}')],
properties: {
name: Id.Id('LuBWqZAu6pz54eiJS5mLv8'),
checked: Id.Id('${checkedPropertyId}'),
},
relations: {
assignees: Id.Id('${assigneesRelationTypeId}'),
},
},
User: {
typeIds: [Id.Id('${userId}')],
properties: {
name: Id.Id('LuBWqZAu6pz54eiJS5mLv8'),
},
}
`;
setMapping(newMapping);
}}
>
Create properties and types
</Button>
</div>
);
};
6 changes: 3 additions & 3 deletions apps/events/src/components/dev-tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ export function DevTool({ spaceId }: { spaceId: string }) {

const spaces = useSelector(store, (state) => state.context.spaces);
const updatesInFlight = useSelector(store, (state) => state.context.updatesInFlight);
const { subscribeToSpace, loading } = useHypergraphApp();
const { subscribeToSpace, isConnecting } = useHypergraphApp();

useEffect(() => {
if (!loading) {
if (!isConnecting) {
subscribeToSpace({ spaceId });
}
}, [loading, subscribeToSpace, spaceId]);
}, [isConnecting, subscribeToSpace, spaceId]);

const space = spaces.find((space) => space.id === spaceId);

Expand Down
10 changes: 10 additions & 0 deletions apps/events/src/components/playground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useQuery } from '@graphprotocol/hypergraph-react';
import { NewsStory } from '../schema';

export const Playground = () => {
const { data: entityData, isLoading, isError } = useQuery(NewsStory, { mode: 'public' });

console.log({ isLoading, isError, entityData });

return <pre className="text-xs">{JSON.stringify(entityData, null, 2)}</pre>;
};
42 changes: 42 additions & 0 deletions apps/events/src/components/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { cn } from '@/lib/utils';
import { motion } from 'framer-motion';

interface SpinnerProps {
size?: 'sm' | 'md' | 'lg' | 'xl';
color?: 'default' | 'primary' | 'secondary' | 'accent' | 'white';
className?: string;
}

export function Spinner({ size = 'md', color = 'primary', className }: SpinnerProps) {
const sizeClasses = {
sm: 'h-4 w-4 border-2',
md: 'h-6 w-6 border-2',
lg: 'h-8 w-8 border-3',
xl: 'h-12 w-12 border-4',
};

const colorClasses = {
default: 'border-muted-foreground/30 border-t-muted-foreground',
primary: 'border-primary/30 border-t-primary',
secondary: 'border-secondary/30 border-t-secondary',
accent: 'border-accent/30 border-t-accent',
white: 'border-white/30 border-t-white',
};

return (
<motion.div
className={cn('rounded-full animate-spin', sizeClasses[size], colorClasses[color], className)}
animate={{ rotate: 360 }}
transition={{
duration: 1,
ease: 'linear',
repeat: Number.POSITIVE_INFINITY,
}}
aria-label="Loading"
>
<span className="sr-only">Loading...</span>
</motion.div>
);
}
48 changes: 48 additions & 0 deletions apps/events/src/components/todo/todos-local.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useHardDeleteEntity, useQuery, useUpdateEntity } from '@graphprotocol/hypergraph-react';
import { Todo2 } from '../../schema';
import { Button } from '../ui/button';

export const TodosLocal = () => {
const updateEntity = useUpdateEntity(Todo2);
const hardDeleteEntity = useHardDeleteEntity();
const { data: todosLocalData, deleted: deletedTodosLocalData } = useQuery(Todo2, { mode: 'local' });

return (
<>
<h2 className="text-2xl font-bold">Todos (Local)</h2>
{todosLocalData.map((todo) => (
<div key={todo.id} className="flex flex-row items-center gap-2">
<h2>{todo.name}</h2>
<div className="text-xs">{todo.id}</div>
{todo.assignees.map((assignee) => (
<span key={assignee.id} className="border rounded-sm mr-1 p-1">
{assignee.name}
</span>
))}
<input
type="checkbox"
checked={todo.checked}
onChange={(e) => updateEntity(todo.id, { checked: e.target.checked })}
/>
{/* @ts-expect-error */}
<div className="text-xs">{todo.__deleted ? 'deleted' : 'not deleted'}</div>
{/* @ts-expect-error */}
<div className="text-xs">{todo.__version}</div>
<Button variant="secondary" size="sm" onClick={() => hardDeleteEntity(todo.id)}>
Hard Delete
</Button>
</div>
))}
<h2 className="text-2xl font-bold">Deleted Todos (Local)</h2>
{deletedTodosLocalData.map((todo) => (
<div key={todo.id} className="flex flex-row items-center gap-2">
<h2>{todo.name}</h2>
<div className="text-xs">{todo.id}</div>
<Button variant="secondary" size="sm" onClick={() => hardDeleteEntity(todo.id)}>
Hard Delete
</Button>
</div>
))}
</>
);
};
81 changes: 81 additions & 0 deletions apps/events/src/components/todo/todos-public-geo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { getSmartAccountWalletClient } from '@/lib/smart-account';
import { Id } from '@graphprotocol/grc-20';
import {
_generateDeleteOps,
publishOps,
useCreateEntity,
useHypergraphSpace,
useQuery,
} from '@graphprotocol/hypergraph-react';
import { useGenerateCreateOps } from '@graphprotocol/hypergraph-react/internal/use-generate-create-ops';
import { Todo2 } from '../../schema';
import { Spinner } from '../spinner';
import { Button } from '../ui/button';

export const TodosPublicGeo = () => {
const space = useHypergraphSpace();
const {
data: dataPublic,
isLoading: isLoadingPublic,
isError: isErrorPublic,
} = useQuery(Todo2, {
mode: 'public',
include: { assignees: {} },
});

const createTodo = useCreateEntity(Todo2);
const generateCreateOps = useGenerateCreateOps(Todo2);

return (
<>
<div className="flex flex-row gap-4 items-center">
<h2 className="text-2xl font-bold">Todos (Public Geo)</h2>
{isLoadingPublic && <Spinner size="sm" />}
</div>
{isErrorPublic && <div>Error loading todos</div>}
{dataPublic.map((todo) => (
<div key={todo.id} className="flex flex-row items-center gap-2">
<h2>{todo.name}</h2>
<div className="text-xs">{todo.id}</div>
<input type="checkbox" checked={todo.checked} readOnly />
{todo.assignees.map((assignee) => (
<span key={assignee.id} className="border rounded-sm mr-1 p-1">
{assignee.name}
</span>
))}

<Button
onClick={async () => {
const smartAccountWalletClient = await getSmartAccountWalletClient();
if (!smartAccountWalletClient) {
throw new Error('Missing smartAccountWalletClient');
}
const ops = await _generateDeleteOps({ id: todo.id, space });
const result = await publishOps({ ops, walletClient: smartAccountWalletClient, space });
console.log('result', result);
}}
>
Delete
</Button>
</div>
))}
<Button
onClick={async () => {
const smartAccountWalletClient = await getSmartAccountWalletClient();
if (!smartAccountWalletClient) {
throw new Error('Missing smartAccountWalletClient');
}
const userId = Id.Id('8zPJjTGLBDPtUcj6q2tghg');
const todo = createTodo({ name: 'New Todo 22', checked: false, assignees: [userId] });
console.log('todo', todo);
const { ops } = generateCreateOps(todo);
console.log('ops', ops);
const result = await publishOps({ ops, walletClient: smartAccountWalletClient, space });
console.log('result', result);
}}
>
Create
</Button>
</>
);
};
45 changes: 45 additions & 0 deletions apps/events/src/components/todo/todos-public-kg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { getSmartAccountWalletClient } from '@/lib/smart-account';
import {
_generateDeleteOps,
publishOps,
useHypergraphSpace,
_useQueryPublicKg as useQueryPublicKg,
} from '@graphprotocol/hypergraph-react';
import { Todo2 } from '../../schema';
import { Spinner } from '../spinner';
import { Button } from '../ui/button';

export const TodosPublicKg = () => {
const space = useHypergraphSpace();
const { data: kgPublicData, isLoading: kgPublicIsLoading, isError: kgPublicIsError } = useQueryPublicKg(Todo2);

return (
<>
<div className="flex flex-row gap-4 items-center">
<h2 className="text-2xl font-bold">Todos (Public KG)</h2>
{kgPublicIsLoading && <Spinner size="sm" />}
</div>
{kgPublicIsError && <div>Error loading todos</div>}
{kgPublicData.map((todo) => (
<div key={todo.id} className="flex flex-row items-center gap-2">
<h2>{todo.name}</h2>
<div className="text-xs">{todo.id}</div>
<input type="checkbox" checked={todo.checked} readOnly />
<Button
onClick={async () => {
const smartAccountWalletClient = await getSmartAccountWalletClient();
if (!smartAccountWalletClient) {
throw new Error('Missing smartAccountWalletClient');
}
const ops = await _generateDeleteOps({ id: todo.id, space });
const result = await publishOps({ ops, walletClient: smartAccountWalletClient, space });
console.log('result', result);
}}
>
Delete
</Button>
</div>
))}
</>
);
};
Loading
Loading