Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"lock": false
"lock": false,
"lint": {
"rules": {
"exclude": ["no-import-prefix"]
}
}
}
23 changes: 23 additions & 0 deletions examples/react-query-todo-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# TODO app with custom semantics

This is an example TODO list application with an RDF data model based on a
custom ontology.

The goal of this application is to showcase how to incorporate advanced
semantics into Linked Data applications.

Semantics elements:

- Ontology: [./semantics/todo_ontology.ttl](./semantics/todo_ontology.ttl)
- ShEx schema: [./semantics/todo_schema.shex](./semantics/todo_schema.shex)
- LDkit schema: [./semantics/todo_schema.ts](./semantics/todo_schema.ts)

The ShEx schema is built using the custom ontology. LDkit schema is then built
using the ShEx schema.

## How to run the application

```
npm install
npm run dev
```
51 changes: 51 additions & 0 deletions examples/react-query-todo-app/semantics/todo_ontology.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix tmo: <http://www.semanticdesktop.org/ontologies/2008/05/20/tmo/v1.1/#> .
@prefix todo: <https://example.com/todo/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<https://example.com/todo/> a owl:Ontology ;
rdfs:label "Todo App Vocabulary" ;
rdfs:comment "A lightweight vocabulary for representing todo items, aligned with Task Management Ontology." ;
owl:imports tmo: .

todo:TodoItem a owl:Class ;
rdfs:subClassOf tmo:Task ;
rdfs:label "Todo Item" ;
rdfs:comment "Represents a single todo item." .

todo:TodoState a owl:Class ;
rdfs:subClassOf tmo:TaskState ;
rdfs:label "Todo State" ;
rdfs:comment "Represents the state of a todo item." .

todo:TodoState_Active a todo:TodoState, owl:NamedIndividual ;
owl:sameAs tmo:TMO_Instance_TaskState_Running ;
rdfs:label "Active" ;
rdfs:comment "Indicates that the todo item is active." .

todo:TodoState_Done a todo:TodoState, owl:NamedIndividual ;
owl:sameAs tmo:TMO_Instance_TaskState_Completed ;
rdfs:label "Done" ;
rdfs:comment "Indicates that the todo item is completed." .

todo:description a owl:DatatypeProperty ;
rdfs:subPropertyOf tmo:hasDescription ;
rdfs:domain todo:TodoItem ;
rdfs:range xsd:string ;
rdfs:label "description" ;
rdfs:comment "Provides a textual description of the todo item." .

todo:dueDate a owl:DatatypeProperty ;
rdfs:subPropertyOf tmo:hasDueDate ;
rdfs:domain todo:TodoItem ;
rdfs:range xsd:dateTime ;
rdfs:label "due date" ;
rdfs:comment "Specifies when the todo item is due." .

todo:state a owl:ObjectProperty ;
rdfs:subPropertyOf tmo:taskState ;
rdfs:domain todo:TodoItem ;
rdfs:range todo:TodoState ;
rdfs:label "todo state" ;
rdfs:comment "Indicates the current state of the todo item." .
14 changes: 14 additions & 0 deletions examples/react-query-todo-app/semantics/todo_schema.shex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
PREFIX : <https://example.com/todo/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

# The TodoItem shape defines the structure of a resource
:TodoItemShape CLOSED {
a [ :TodoItem ] ;
:description xsd:string ;
:state @:TodoStateShape ;
:dueDate xsd:dateTime ?
}

:TodoStateShape CLOSED {
a [ :TodoState ]
}
35 changes: 35 additions & 0 deletions examples/react-query-todo-app/semantics/todo_schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createNamespace, ldkit, xsd } from "ldkit/namespaces";

export const todo = createNamespace(
{
iri: "https://example.com/todo/",
prefix: "todo:",
terms: [
"TodoItem",
"TodoState",
"TodoState_Active",
"TodoState_Done",
"description",
"state",
"dueDate",
],
} as const,
);

export const TodoStateSchema = {
"@type": todo.TodoState,
} as const;

export const TodoItemSchema = {
"@type": todo.TodoItem,
description: todo.description,
state: {
"@id": todo.state,
"@type": ldkit.IRI,
},
dueDate: {
"@id": todo.dueDate,
"@type": xsd.dateTime,
"@optional": true,
},
} as const;
4 changes: 2 additions & 2 deletions examples/react-query-todo-app/src/components/Add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { KeyboardEvent, useCallback, useRef, useState } from "react";
import styled from "@emotion/styled";
import { useQueryClient } from "@tanstack/react-query";

import { getRandomId, Todos } from "../store";
import { ACTIVE, getRandomId, Todos } from "../store";
import { Button, InvisibleButton, MootButton, Row, RowContent } from "./UI";

import { AddIcon, CircleIcon } from "./Icons";
Expand Down Expand Up @@ -49,7 +49,7 @@ export const Add: React.FC = () => {
Todos.insert({
$id: getRandomId(),
description: inputValue,
done: false,
state: ACTIVE,
}).then(() => {
queryClient.invalidateQueries({ queryKey: ["todos"] });
});
Expand Down
36 changes: 29 additions & 7 deletions examples/react-query-todo-app/src/components/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import styled from "@emotion/styled";

import { useQuery, useQueryClient } from "@tanstack/react-query";

import { TodoInterface, Todos } from "../store";
import { Button, InvisibleButton, Row, RowContent } from "./UI";
import { ACTIVE, DONE, isDone, TodoInterface, Todos } from "../store";
import { Button, DateInput, DateView, Row, RowContent } from "./UI";
import { CheckedIcon, CircleIcon, RemoveIcon } from "./Icons";

const List = styled.div`
Expand All @@ -31,27 +31,49 @@ const Item: React.FC<ItemProps> = ({ item }) => {
const handleCheckboxClicked = useCallback(() => {
Todos.update({
$id: item.$id,
done: !item.done,
state: isDone(item) ? ACTIVE : DONE,
}).then(() => {
queryClient.invalidateQueries({ queryKey: ["todos"] });
});
}, [item, queryClient]);

const handleDateChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const dateValue = event.target.value;
const dueDate = dateValue == "" ? null : new Date(dateValue);
console.log(dueDate);
Todos.update({
$id: item.$id,
dueDate,
}).then(() => {
queryClient.invalidateQueries({ queryKey: ["todos"] });
});
},
[item, queryClient],
);

return (
<Row>
<Button onClick={handleCheckboxClicked}>
{item.done ? <CheckedIcon /> : <CircleIcon />}
{isDone(item) ? <CheckedIcon /> : <CircleIcon />}
</Button>
<RowContent>
{item.done ? <Done>{item.description}</Done> : item.description}
{isDone(item) ? <Done>{item.description}</Done> : item.description}
</RowContent>
{item.done
{isDone(item)
? (
<Button onClick={handleDeleteClicked}>
<RemoveIcon />
</Button>
)
: <InvisibleButton />}
: (
<div>
<DateView>
{item.dueDate ? item.dueDate.toLocaleDateString() : null}
</DateView>
<DateInput onChange={handleDateChange} type="date" />
</div>
)}
</Row>
);
};
Expand Down
14 changes: 13 additions & 1 deletion examples/react-query-todo-app/src/components/UI.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled from "@emotion/styled";
import type { Component, ReactElement, ReactNode } from "react";
import type { ReactNode } from "react";

export const MootButton = styled.button`
display: block;
Expand Down Expand Up @@ -43,6 +43,18 @@ export const RowContent = styled.div`
font-size: 18px;
`;

export const DateView = styled.span`
font-size: 16px;
color: #666;
`;

export const DateInput = styled.input`
width: 22px;
margin-left: 10px;
font-size: 18px;
border: none;
`;

type GroupProps = {
start?: ReactNode;
content?: ReactNode;
Expand Down
39 changes: 12 additions & 27 deletions examples/react-query-todo-app/src/store.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,27 @@
import {
type Options,
createLens,
createNamespace,
type SchemaInterface,
} from "ldkit";
import { xsd } from "ldkit/namespaces";
import { createLens, type Options, type SchemaInterface } from "ldkit";
import { QueryEngine as Comunica } from "@comunica/query-sparql-rdfjs";

import { N3 } from "ldkit/rdf";

const t = createNamespace(
{
iri: "https://todos/",
prefix: "t:",
terms: ["Todo", "description", "done"],
} as const,
);

const TodoSchema = {
"@type": t.Todo,
description: t.description,
done: {
"@id": t.done,
"@type": xsd.boolean,
},
} as const;
import { todo, TodoItemSchema } from "../semantics/todo_schema";

export const store = new N3.Store();

const options: Options = {
sources: [store],
engine: new Comunica(),
logQuery: (query) => console.log(query)
logQuery: (query) => console.log(query),
};

export type TodoInterface = SchemaInterface<typeof TodoSchema>;
export type TodoInterface = SchemaInterface<typeof TodoItemSchema>;

export const Todos = createLens(TodoSchema, options);
export const Todos = createLens(TodoItemSchema, options);

export const getRandomId = () =>
`https://todos/${1000 + Math.floor(Math.random() * 1000)}`;
`${todo.$iri}${1000 + Math.floor(Math.random() * 1000)}`;

export const isDone = (item: TodoInterface) =>
item.state === todo.TodoState_Done;

export const DONE = todo.TodoState_Done;
export const ACTIVE = todo.TodoState_Active;