First commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.parcel-cache
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# production
|
||||||
|
dist
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
## Simple app for NextGraph
|
||||||
|
|
||||||
|
A simple app that use Refine providers compatible with NextGraph
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm install
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="description" content="Web site created using refine" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<title>refine authentication example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm run dev` or `yarn dev`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
--></body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "antd-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "refine build",
|
||||||
|
"dev": "refine dev",
|
||||||
|
"refine": "refine",
|
||||||
|
"start": "refine start",
|
||||||
|
"build:orm": "rdf-orm build --input ./src/shapes/shex --output ./src/shapes/orm"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.5.1",
|
||||||
|
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||||
|
"@ng-org/orm": "0.1.2-alpha.19",
|
||||||
|
"@ng-org/web": "0.1.2-alpha.13",
|
||||||
|
"@ng-org/refine-providers": "workspace:*",
|
||||||
|
"@refinedev/antd": "^6.0.3",
|
||||||
|
"@refinedev/cli": "^2.16.52",
|
||||||
|
"@refinedev/core": "^5.0.12",
|
||||||
|
"@refinedev/react-router": "^2.0.4",
|
||||||
|
"@uiw/react-md-editor": "^4.0.8",
|
||||||
|
"antd": "^5.23.0",
|
||||||
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
|
"react-router": "^7.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ng-org/shex-orm": "0.1.2-alpha.8",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19.1.0",
|
||||||
|
"@types/react-dom": "^19.1.0",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"vite": "^5.4.15"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"short_name": "refine authentication example",
|
||||||
|
"name": "refine authentication example",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import {
|
||||||
|
Refine,
|
||||||
|
Authenticated,
|
||||||
|
} from "@refinedev/core";
|
||||||
|
import {
|
||||||
|
useNotificationProvider,
|
||||||
|
ThemedLayout,
|
||||||
|
ErrorComponent,
|
||||||
|
RefineThemes,
|
||||||
|
} from "@refinedev/antd";
|
||||||
|
import {
|
||||||
|
DashboardOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
|
||||||
|
import routerProvider, {
|
||||||
|
NavigateToResource,
|
||||||
|
CatchAllNavigate,
|
||||||
|
UnsavedChangesNotifier,
|
||||||
|
DocumentTitleHandler,
|
||||||
|
} from "@refinedev/react-router";
|
||||||
|
import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router";
|
||||||
|
import { App as AntdApp, ConfigProvider } from "antd";
|
||||||
|
|
||||||
|
import "@ant-design/v5-patch-for-react-19";
|
||||||
|
import "@refinedev/antd/dist/reset.css";
|
||||||
|
|
||||||
|
import { authProvider, dataProvider, liveProvider } from "@ng-org/refine-providers";
|
||||||
|
|
||||||
|
import dataModels from "./shapes/data-models";
|
||||||
|
import AuthPage from "./components/AuthPage";
|
||||||
|
import { PostList, PostCreate, PostEdit, PostShow } from "../src/pages/posts";
|
||||||
|
import { DashboardPage } from "../src/pages/dashboard";
|
||||||
|
|
||||||
|
import { getSession } from "./utils/initNg";
|
||||||
|
|
||||||
|
const App: React.FC = () => (
|
||||||
|
<BrowserRouter basename={import.meta.env.MODE === 'production' ? '/nextgraph-refine-app/' : '/'}>
|
||||||
|
<ConfigProvider theme={RefineThemes.Blue}>
|
||||||
|
<AntdApp>
|
||||||
|
<Refine
|
||||||
|
// authProvider={authProvider}
|
||||||
|
dataProvider={dataProvider({ dataModels, getSession })}
|
||||||
|
liveProvider={liveProvider({ dataModels })}
|
||||||
|
routerProvider={routerProvider}
|
||||||
|
resources={[
|
||||||
|
// {
|
||||||
|
// name: "dashboard",
|
||||||
|
// list: "/",
|
||||||
|
// meta: {
|
||||||
|
// label: "Dashboard",
|
||||||
|
// icon: <DashboardOutlined />,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: "posts",
|
||||||
|
list: "/posts",
|
||||||
|
create: "/posts/create",
|
||||||
|
show: "/posts/show/:id",
|
||||||
|
edit: "/posts/edit/:id",
|
||||||
|
meta: {
|
||||||
|
canDelete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
notificationProvider={useNotificationProvider}
|
||||||
|
options={{
|
||||||
|
syncWithLocation: true,
|
||||||
|
warnWhenUnsavedChanges: true,
|
||||||
|
liveMode: "auto",
|
||||||
|
disableTelemetry: true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
// <Authenticated
|
||||||
|
// key="authenticated-routes"
|
||||||
|
// fallback={<CatchAllNavigate to="/login" />}
|
||||||
|
// >
|
||||||
|
<ThemedLayout>
|
||||||
|
<Outlet />
|
||||||
|
</ThemedLayout>
|
||||||
|
// </Authenticated>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route index element={<Navigate to="/" />} />
|
||||||
|
{/* <Route index element={<DashboardPage />} /> */}
|
||||||
|
|
||||||
|
<Route path="/posts">
|
||||||
|
<Route index element={<PostList />} />
|
||||||
|
<Route path="create" element={<PostCreate />} />
|
||||||
|
<Route path="edit/:id" element={<PostEdit />} />
|
||||||
|
<Route path="show/:id" element={<PostShow />} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* <Route
|
||||||
|
element={
|
||||||
|
<Authenticated key="auth-pages" fallback={<Outlet />}>
|
||||||
|
<NavigateToResource resource="posts" />
|
||||||
|
</Authenticated>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path="/login" element={<AuthPage />} />
|
||||||
|
<Route path="/register" element={<AuthPage />} />
|
||||||
|
</Route> */}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
// <Authenticated key="catch-all">
|
||||||
|
<ThemedLayout>
|
||||||
|
<Outlet />
|
||||||
|
</ThemedLayout>
|
||||||
|
// </Authenticated>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path="*" element={<ErrorComponent />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
<UnsavedChangesNotifier />
|
||||||
|
<DocumentTitleHandler />
|
||||||
|
</Refine>
|
||||||
|
</AntdApp>
|
||||||
|
</ConfigProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type LoginPageProps,
|
||||||
|
type LoginFormTypes,
|
||||||
|
} from "@refinedev/core";
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Layout,
|
||||||
|
Card,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
type CardProps,
|
||||||
|
type LayoutProps,
|
||||||
|
type FormProps,
|
||||||
|
theme,
|
||||||
|
} from "antd";
|
||||||
|
import { useLogin, useTranslate } from "@refinedev/core";
|
||||||
|
import { ThemedTitle } from "@refinedev/antd";
|
||||||
|
|
||||||
|
export const layoutStyles: CSSProperties = {};
|
||||||
|
|
||||||
|
export const containerStyles: CSSProperties = {
|
||||||
|
maxWidth: "400px",
|
||||||
|
margin: "auto",
|
||||||
|
padding: "32px",
|
||||||
|
boxShadow:
|
||||||
|
"0px 2px 4px rgba(0, 0, 0, 0.02), 0px 1px 6px -1px rgba(0, 0, 0, 0.02), 0px 1px 2px rgba(0, 0, 0, 0.03)",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const headStyles: CSSProperties = {
|
||||||
|
borderBottom: 0,
|
||||||
|
padding: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bodyStyles: CSSProperties = { padding: 0 };
|
||||||
|
|
||||||
|
export const titleStyles: CSSProperties = {
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: 0,
|
||||||
|
fontSize: "24px",
|
||||||
|
lineHeight: "32px",
|
||||||
|
fontWeight: 700,
|
||||||
|
overflowWrap: "break-word",
|
||||||
|
hyphens: "manual",
|
||||||
|
textOverflow: "unset",
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
};
|
||||||
|
|
||||||
|
type LoginProps = LoginPageProps<LayoutProps, CardProps, FormProps>;
|
||||||
|
|
||||||
|
export const AuthPage: React.FC<LoginProps> = ({
|
||||||
|
contentProps,
|
||||||
|
wrapperProps,
|
||||||
|
renderContent,
|
||||||
|
formProps,
|
||||||
|
title,
|
||||||
|
hideForm,
|
||||||
|
mutationVariables,
|
||||||
|
}) => {
|
||||||
|
const { token } = theme.useToken();
|
||||||
|
const [form] = Form.useForm<LoginFormTypes>();
|
||||||
|
const translate = useTranslate();
|
||||||
|
|
||||||
|
const { mutate: login, isPending } = useLogin<LoginFormTypes>();
|
||||||
|
|
||||||
|
const PageTitle =
|
||||||
|
title === false ? null : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
marginBottom: "32px",
|
||||||
|
fontSize: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title ?? <ThemedTitle collapsed={false} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CardContent = (
|
||||||
|
<Card
|
||||||
|
// title={CardTitle}
|
||||||
|
styles={{
|
||||||
|
header: headStyles,
|
||||||
|
body: bodyStyles,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
...containerStyles,
|
||||||
|
backgroundColor: token.colorBgElevated,
|
||||||
|
}}
|
||||||
|
{...(contentProps ?? {})}
|
||||||
|
>
|
||||||
|
<Form<LoginFormTypes>
|
||||||
|
layout="vertical"
|
||||||
|
form={form}
|
||||||
|
onFinish={(values) => login({ ...values, ...mutationVariables })}
|
||||||
|
requiredMark={false}
|
||||||
|
initialValues={{
|
||||||
|
brokerUrl: 'https://nextgraph.eu'
|
||||||
|
}}
|
||||||
|
{...formProps}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="brokerUrl"
|
||||||
|
label={translate("pages.login.fields.brokerUrl", "Broker URL")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: translate(
|
||||||
|
"pages.login.errors.requiredBrokerUrl",
|
||||||
|
"Broker URL is required",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "url",
|
||||||
|
message: translate(
|
||||||
|
"pages.login.errors.validBrokerUrl",
|
||||||
|
"Invalid broker URL",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
size="large"
|
||||||
|
placeholder={translate("pages.login.fields.brokerUrl", "Broker URL")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={isPending}
|
||||||
|
block
|
||||||
|
>
|
||||||
|
{translate("pages.login.signin", "Sign in")}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout style={layoutStyles} {...(wrapperProps ?? {})}>
|
||||||
|
<Row
|
||||||
|
justify="center"
|
||||||
|
align={hideForm ? "top" : "middle"}
|
||||||
|
style={{
|
||||||
|
padding: "16px 0",
|
||||||
|
minHeight: "100dvh",
|
||||||
|
paddingTop: hideForm ? "15dvh" : "16px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Col xs={22}>
|
||||||
|
{renderContent ? (
|
||||||
|
renderContent(CardContent, PageTitle)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{PageTitle}
|
||||||
|
{CardContent}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthPage;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
const container = document.getElementById("root");
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const root = createRoot(container!);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { useGetIdentity, usePermissions } from "@refinedev/core";
|
||||||
|
import { Row, Col, Card, Avatar, Typography, Space } from "antd";
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
export const DashboardPage: React.FC = () => {
|
||||||
|
const { data: identity } = useGetIdentity<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
avatar: string;
|
||||||
|
}>();
|
||||||
|
const permissions = usePermissions<string[], { permissions: string[] }>({
|
||||||
|
params: { permissions: ["admin"] },
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={20}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card
|
||||||
|
title="Identity"
|
||||||
|
style={{ height: "300px", borderRadius: "15px" }}
|
||||||
|
headStyle={{ textAlign: "center" }}
|
||||||
|
>
|
||||||
|
<Space align="center" direction="horizontal">
|
||||||
|
<Avatar size="large" src={identity?.avatar} />
|
||||||
|
<Text>{identity?.name}</Text>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card
|
||||||
|
title="Permissions"
|
||||||
|
style={{ height: "300px", borderRadius: "15px" }}
|
||||||
|
headStyle={{ textAlign: "center" }}
|
||||||
|
>
|
||||||
|
<Text>{permissions.data}</Text>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Create, useForm } from "@refinedev/antd";
|
||||||
|
import { Form, Input } from "antd";
|
||||||
|
import MDEditor from "@uiw/react-md-editor";
|
||||||
|
import type { Note } from "../../shapes/orm/post.typings";
|
||||||
|
|
||||||
|
export const PostCreate = () => {
|
||||||
|
const { formProps, saveButtonProps } = useForm<Note>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Create saveButtonProps={saveButtonProps}>
|
||||||
|
<Form {...formProps} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
label="Title"
|
||||||
|
name="title"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter a post title" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Content"
|
||||||
|
name="content"
|
||||||
|
>
|
||||||
|
<MDEditor data-color-mode="light" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Create>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React, { useCallback, useState } from "react";
|
||||||
|
import { Edit, useForm, RefreshButton } from "@refinedev/antd";
|
||||||
|
import { useNavigation, useNotification, type LiveEvent } from "@refinedev/core";
|
||||||
|
import { Alert, Form, Input } from "antd";
|
||||||
|
import MDEditor from "@uiw/react-md-editor";
|
||||||
|
import type { Note } from "../../shapes/orm/post.typings";
|
||||||
|
|
||||||
|
export const PostEdit = () => {
|
||||||
|
const [deprecated, setDeprecated] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { list } = useNavigation();
|
||||||
|
const { open } = useNotification();
|
||||||
|
|
||||||
|
const onLiveEvent = useCallback((event: LiveEvent) => {
|
||||||
|
console.log('onLiveEvent', event);
|
||||||
|
if (event.type === "deleted") {
|
||||||
|
open?.({
|
||||||
|
type: "error",
|
||||||
|
message: "This post has been deleted",
|
||||||
|
});
|
||||||
|
list("posts");
|
||||||
|
} else if (event.type === "updated") {
|
||||||
|
setDeprecated(true);
|
||||||
|
}
|
||||||
|
}, [open, list]);
|
||||||
|
|
||||||
|
const { formProps, saveButtonProps, query: queryResult } = useForm<Note>({
|
||||||
|
liveMode: 'manual',
|
||||||
|
onLiveEvent,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
queryResult?.refetch();
|
||||||
|
setDeprecated(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Edit saveButtonProps={saveButtonProps}>
|
||||||
|
<Form {...formProps} layout="vertical">
|
||||||
|
{deprecated && (
|
||||||
|
<Alert
|
||||||
|
message="This post has been updated. Refresh to see changes."
|
||||||
|
type="warning"
|
||||||
|
style={{ marginBottom: 20 }}
|
||||||
|
action={<RefreshButton size="small" onClick={handleRefresh} />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Form.Item
|
||||||
|
label="Title"
|
||||||
|
name="title"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Content"
|
||||||
|
name="content"
|
||||||
|
>
|
||||||
|
<MDEditor data-color-mode="light" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Edit>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./list";
|
||||||
|
export * from "./create";
|
||||||
|
export * from "./edit";
|
||||||
|
export * from "./show";
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
List,
|
||||||
|
useTable,
|
||||||
|
EditButton,
|
||||||
|
ShowButton,
|
||||||
|
DeleteButton,
|
||||||
|
CreateButton,
|
||||||
|
} from "@refinedev/antd";
|
||||||
|
import { useNavigation } from "@refinedev/core";
|
||||||
|
import { Table, Space } from "antd";
|
||||||
|
import type { Note } from "../../shapes/orm/post.typings";
|
||||||
|
|
||||||
|
const shortenNuri = (nuri: string) => {
|
||||||
|
return nuri?.split(':')[3]?.slice(0, 7);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PostList = () => {
|
||||||
|
const { tableProps } = useTable<Note>();
|
||||||
|
const { show } = useNavigation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List headerButtons={<CreateButton />}>
|
||||||
|
<Table
|
||||||
|
{...tableProps}
|
||||||
|
rowKey="@graph"
|
||||||
|
onRow={(record) => ({
|
||||||
|
onClick: () => show("posts", record["@graph"]),
|
||||||
|
style: { cursor: "pointer" },
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Table.Column dataIndex="@graph" title="ID" render={shortenNuri} />
|
||||||
|
<Table.Column dataIndex="title" title="Title" />
|
||||||
|
<Table.Column dataIndex="content" title="Content" />
|
||||||
|
<Table.Column<Note>
|
||||||
|
title="Actions"
|
||||||
|
dataIndex="actions"
|
||||||
|
align="right"
|
||||||
|
render={(_, record) => (
|
||||||
|
<Space onClick={(e) => e.stopPropagation()}>
|
||||||
|
<EditButton hideText size="small" recordItemId={record['@graph']} />
|
||||||
|
<ShowButton hideText size="small" recordItemId={record['@graph']} />
|
||||||
|
<DeleteButton hideText size="small" recordItemId={record['@graph']} />
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { useShow, useNavigation, useNotification, type LiveEvent } from "@refinedev/core";
|
||||||
|
import { Show, MarkdownField } from "@refinedev/antd";
|
||||||
|
import { Typography } from "antd";
|
||||||
|
import type { Note } from "../../shapes/orm/post.typings";
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
export const PostShow = () => {
|
||||||
|
const { list } = useNavigation();
|
||||||
|
const { open } = useNotification();
|
||||||
|
|
||||||
|
const onLiveEvent = useCallback((event: LiveEvent) => {
|
||||||
|
if (event.type === "deleted") {
|
||||||
|
open?.({
|
||||||
|
type: "error",
|
||||||
|
message: "This post has been deleted",
|
||||||
|
});
|
||||||
|
list("posts");
|
||||||
|
}
|
||||||
|
}, [open, list]);
|
||||||
|
|
||||||
|
const { query: queryResult } = useShow<Note>({ liveMode: "auto", onLiveEvent });
|
||||||
|
const { data, isLoading } = queryResult;
|
||||||
|
const record = data?.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show isLoading={isLoading}>
|
||||||
|
<Title level={5}>Id</Title>
|
||||||
|
<Text>{record?.['@graph']}</Text>
|
||||||
|
<Title level={5}>Title</Title>
|
||||||
|
<Text>{record?.title}</Text>
|
||||||
|
<Title level={5}>Content</Title>
|
||||||
|
<MarkdownField value={record?.content} />
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { DataModels } from "@ng-org/refine-providers";
|
||||||
|
import { NoteShapeType } from "./orm/post.shapeTypes";
|
||||||
|
|
||||||
|
const dataModels : DataModels = {
|
||||||
|
posts: {
|
||||||
|
shapeType: NoteShapeType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default dataModels;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import type { Schema } from "@ng-org/shex-orm";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =============================================================================
|
||||||
|
* postSchema: Schema for post
|
||||||
|
* =============================================================================
|
||||||
|
*/
|
||||||
|
export const postSchema = {
|
||||||
|
"https://www.w3.org/ns/activitystreams#Note": {
|
||||||
|
iri: "https://www.w3.org/ns/activitystreams#Note",
|
||||||
|
predicates: [
|
||||||
|
{
|
||||||
|
dataTypes: [
|
||||||
|
{
|
||||||
|
valType: "iri",
|
||||||
|
literals: ["https://www.w3.org/ns/activitystreams#Note"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
maxCardinality: 1,
|
||||||
|
minCardinality: 1,
|
||||||
|
iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
|
||||||
|
readablePredicate: "@type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataTypes: [
|
||||||
|
{
|
||||||
|
valType: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
maxCardinality: 1,
|
||||||
|
minCardinality: 1,
|
||||||
|
iri: "https://www.w3.org/ns/activitystreams#title",
|
||||||
|
readablePredicate: "title",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataTypes: [
|
||||||
|
{
|
||||||
|
valType: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
maxCardinality: 1,
|
||||||
|
minCardinality: 0,
|
||||||
|
iri: "https://www.w3.org/ns/activitystreams#content",
|
||||||
|
readablePredicate: "content",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as const satisfies Schema;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ShapeType } from "@ng-org/shex-orm";
|
||||||
|
import { postSchema } from "./post.schema";
|
||||||
|
import type { Note } from "./post.typings";
|
||||||
|
|
||||||
|
// ShapeTypes for post
|
||||||
|
export const NoteShapeType = {
|
||||||
|
schema: postSchema,
|
||||||
|
shape: "https://www.w3.org/ns/activitystreams#Note",
|
||||||
|
} as const satisfies ShapeType<Note>;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
export type IRI = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* =============================================================================
|
||||||
|
* Typescript Typings for post
|
||||||
|
* =============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note Type
|
||||||
|
*/
|
||||||
|
export interface Note {
|
||||||
|
/**
|
||||||
|
* The graph NURI.
|
||||||
|
*/
|
||||||
|
readonly "@graph": IRI;
|
||||||
|
/**
|
||||||
|
* The subject IRI.
|
||||||
|
*/
|
||||||
|
readonly "@id": IRI;
|
||||||
|
/**
|
||||||
|
* Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type
|
||||||
|
*/
|
||||||
|
"@type": "https://www.w3.org/ns/activitystreams#Note";
|
||||||
|
/**
|
||||||
|
* The title of the post
|
||||||
|
*
|
||||||
|
* Original IRI: https://www.w3.org/ns/activitystreams#title
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* The content of the post
|
||||||
|
*
|
||||||
|
* Original IRI: https://www.w3.org/ns/activitystreams#content
|
||||||
|
*/
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
PREFIX as: <https://www.w3.org/ns/activitystreams#>
|
||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
||||||
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||||
|
|
||||||
|
as:Note {
|
||||||
|
a [as:Note] ;
|
||||||
|
as:title xsd:string
|
||||||
|
// rdfs:comment "The title of the post" ;
|
||||||
|
as:content xsd:string ?
|
||||||
|
// rdfs:comment "The content of the post" ;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { ng, init, Session } from "@ng-org/web";
|
||||||
|
import { initNg } from "@ng-org/orm";
|
||||||
|
|
||||||
|
export let session: Session | undefined;
|
||||||
|
|
||||||
|
init(
|
||||||
|
async (event: any) => {
|
||||||
|
session = event.session;
|
||||||
|
// The ORM needs to have access to ng,
|
||||||
|
// the interface to the engine running in WASM.
|
||||||
|
initNg(ng, session!);
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getSession = () => session;
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"name": "@ng-org/refine-providers",
|
||||||
|
"version": "0.1.2",
|
||||||
|
"private": false,
|
||||||
|
"type": "module",
|
||||||
|
"authors": [
|
||||||
|
"Sébastien Rosset"
|
||||||
|
],
|
||||||
|
"license": "MIT/Apache-2.0",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./auth-provider": {
|
||||||
|
"types": "./dist/auth-provider.d.ts",
|
||||||
|
"default": "./dist/auth-provider.js"
|
||||||
|
},
|
||||||
|
"./data-provider": {
|
||||||
|
"types": "./dist/data-provider.d.ts",
|
||||||
|
"default": "./dist/data-provider.js"
|
||||||
|
},
|
||||||
|
"./live-provider": {
|
||||||
|
"types": "./dist/live-provider.d.ts",
|
||||||
|
"default": "./dist/live-provider.js"
|
||||||
|
},
|
||||||
|
"./types": {
|
||||||
|
"types": "./dist/types.d.ts"
|
||||||
|
},
|
||||||
|
"./utils": {
|
||||||
|
"types": "./dist/utils.d.ts",
|
||||||
|
"default": "./dist/utils.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"index": {
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"distDir": "dist",
|
||||||
|
"context": "browser",
|
||||||
|
"outputFormat": "esmodule",
|
||||||
|
"isLibrary": true,
|
||||||
|
"includeNodeModules": false,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"auth-provider": {
|
||||||
|
"source": "src/auth-provider.ts",
|
||||||
|
"distDir": "dist",
|
||||||
|
"context": "browser",
|
||||||
|
"outputFormat": "esmodule",
|
||||||
|
"isLibrary": true,
|
||||||
|
"includeNodeModules": false,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"data-provider": {
|
||||||
|
"source": "src/data-provider.ts",
|
||||||
|
"distDir": "dist",
|
||||||
|
"context": "browser",
|
||||||
|
"outputFormat": "esmodule",
|
||||||
|
"isLibrary": true,
|
||||||
|
"includeNodeModules": false,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"live-provider": {
|
||||||
|
"source": "src/live-provider.ts",
|
||||||
|
"distDir": "dist",
|
||||||
|
"context": "browser",
|
||||||
|
"outputFormat": "esmodule",
|
||||||
|
"isLibrary": true,
|
||||||
|
"includeNodeModules": false,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"source": "src/utils.ts",
|
||||||
|
"distDir": "dist",
|
||||||
|
"context": "browser",
|
||||||
|
"outputFormat": "esmodule",
|
||||||
|
"isLibrary": true,
|
||||||
|
"includeNodeModules": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "rm -rf dist .parcel-cache && parcel build && tsc -p tsconfig.build.json",
|
||||||
|
"watch": "concurrently -n parcel,tsc -c blue,yellow \"parcel watch --no-cache\" \"tsc -p tsconfig.build.json --watch --preserveWatchOutput\"",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@refinedev/core": "^5.0.12",
|
||||||
|
"@ng-org/alien-deepsignals": "0.1.2-alpha.12",
|
||||||
|
"@ng-org/orm": "0.1.2-alpha.19",
|
||||||
|
"@ng-org/shex-orm": "0.1.2-alpha.8",
|
||||||
|
"@ng-org/web": "0.1.2-alpha.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ng-org/alien-deepsignals": "0.1.2-alpha.12",
|
||||||
|
"@ng-org/orm": "0.1.2-alpha.19",
|
||||||
|
"@ng-org/shex-orm": "0.1.2-alpha.8",
|
||||||
|
"@ng-org/web": "0.1.2-alpha.13",
|
||||||
|
"@refinedev/core": "^5.0.12",
|
||||||
|
"concurrently": "^10.0.3",
|
||||||
|
"parcel": "^2.16.4",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+9532
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- examples/*
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import {
|
||||||
|
type AuthProvider,
|
||||||
|
} from "@refinedev/core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ng,
|
||||||
|
init as initNgWeb,
|
||||||
|
type Session as NextGraphSession,
|
||||||
|
} from "@ng-org/web";
|
||||||
|
import { initNg as initNgSignals } from "@ng-org/orm";
|
||||||
|
|
||||||
|
export type NgAuthProvider = AuthProvider & {
|
||||||
|
getSession: () => NextGraphSession | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The session with the NextGraph engine or undefined if not loaded. */
|
||||||
|
export let session: NextGraphSession | undefined;
|
||||||
|
|
||||||
|
export let resolveSessionPromise: (
|
||||||
|
value: NextGraphSession | PromiseLike<NextGraphSession>,
|
||||||
|
) => void;
|
||||||
|
export let rejectSessionPromise: (reason?: any) => void;
|
||||||
|
|
||||||
|
/** Resolves to the current NextGraph session. */
|
||||||
|
export let sessionPromise: Promise<NextGraphSession> = new Promise(
|
||||||
|
(resolve, reject) => {
|
||||||
|
resolveSessionPromise = resolve;
|
||||||
|
rejectSessionPromise = reject;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const authProvider: NgAuthProvider = {
|
||||||
|
login: async ({ brokerUrl }) => {
|
||||||
|
// try {
|
||||||
|
// await initNgWeb(
|
||||||
|
// async (event: any) => {
|
||||||
|
// session = event.session;
|
||||||
|
// session!.ng ??= ng;
|
||||||
|
// localStorage.setItem("logged", "true");
|
||||||
|
// console.log('session', session);
|
||||||
|
// resolveSessionPromise(session!);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// initNgSignals(ng, session!);
|
||||||
|
// },
|
||||||
|
// true,
|
||||||
|
// [],
|
||||||
|
// );
|
||||||
|
|
||||||
|
|
||||||
|
// } catch (error) {
|
||||||
|
// rejectSessionPromise(error);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
register: async ({ brokerUrl }) => {
|
||||||
|
console.log('brokerUrl', brokerUrl);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
logout: async () => {
|
||||||
|
localStorage.removeItem("logged");
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
redirectTo: "/login",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onError: async (error) => {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
return {
|
||||||
|
logout: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { error };
|
||||||
|
},
|
||||||
|
check: async () => {
|
||||||
|
return ({ authenticated: localStorage.getItem("logged") === 'true' });
|
||||||
|
},
|
||||||
|
getPermissions: async (params) => params?.permissions,
|
||||||
|
getIdentity: async () => ({
|
||||||
|
id: 1,
|
||||||
|
name: "Jane Doe",
|
||||||
|
avatar:
|
||||||
|
"https://unsplash.com/photos/IWLOvomUmWU/download?force=true&w=640",
|
||||||
|
}),
|
||||||
|
getSession : () => session
|
||||||
|
};
|
||||||
|
|
||||||
|
export default authProvider;
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { DataProvider } from "@refinedev/core";
|
||||||
|
import { ng } from "@ng-org/web";
|
||||||
|
import { getObjects } from "@ng-org/orm";
|
||||||
|
import { addIdToObject, isGraphKey, getSignalObject } from "./utils";
|
||||||
|
import { DataProviderConfig } from "./types";
|
||||||
|
|
||||||
|
const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProvider => ({
|
||||||
|
getList: async ({ resource, pagination, sorters, filters, meta }) => {
|
||||||
|
console.log('list', resource, pagination, sorters, filters, meta);
|
||||||
|
|
||||||
|
const objects = await getObjects(dataModels[resource].shapeType, "");
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: [...objects].map(addIdToObject) as any,
|
||||||
|
total: 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
create: async ({ resource, variables, meta }) => {
|
||||||
|
console.log("create", resource, variables, meta);
|
||||||
|
|
||||||
|
const session = getSession();
|
||||||
|
console.log('session', session);
|
||||||
|
|
||||||
|
const docNuri = await ng.doc_create(
|
||||||
|
session!.session_id,
|
||||||
|
"Graph",
|
||||||
|
"data:graph",
|
||||||
|
"store", // Private store
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const set = await getSignalObject(dataModels[resource].shapeType, [docNuri]);
|
||||||
|
|
||||||
|
set.add({
|
||||||
|
"@graph": docNuri,
|
||||||
|
"@type": "https://www.w3.org/ns/activitystreams#Note",
|
||||||
|
"@id": "",
|
||||||
|
...variables
|
||||||
|
});
|
||||||
|
|
||||||
|
const newData = set.first();
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: addIdToObject({ ...newData }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
update: async ({ resource, id, variables, meta }) => {
|
||||||
|
console.log('update', resource, id, variables, meta);
|
||||||
|
|
||||||
|
const set = await getSignalObject(dataModels[resource].shapeType, [`${id}`]);
|
||||||
|
|
||||||
|
const oldData = set.first()!;
|
||||||
|
|
||||||
|
const objectId = oldData["@id"];
|
||||||
|
|
||||||
|
const newData = {
|
||||||
|
"@graph": id,
|
||||||
|
"@type": "https://www.w3.org/ns/activitystreams#Note",
|
||||||
|
"@id": objectId,
|
||||||
|
...variables
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete old data and add new data to the set
|
||||||
|
// Hopefully the ORM will handle this intelligently
|
||||||
|
set.delete(oldData);
|
||||||
|
set.add(newData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: addIdToObject(newData)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
deleteOne: async ({ resource, id, variables, meta }) => {
|
||||||
|
console.log('deleteOne', resource, id, variables, meta);
|
||||||
|
|
||||||
|
const set = await getSignalObject(dataModels[resource].shapeType, [`${id}`]);
|
||||||
|
|
||||||
|
const oldData = set.first()!;
|
||||||
|
|
||||||
|
set.delete(oldData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: addIdToObject({ ...oldData })
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getOne: async ({ resource, id, meta }) => {
|
||||||
|
console.log('getOne', resource, id, meta);
|
||||||
|
|
||||||
|
// The ID should be a graph URI, but if it's an object key, we can use the subjects filter to the the object
|
||||||
|
const objects = await getObjects(dataModels[resource].shapeType, isGraphKey(id) ? `${id}` : { subjects: [`${id}`] });
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: addIdToObject([...objects][0])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getApiUrl: () => "",
|
||||||
|
// optional methods
|
||||||
|
// getMany: ({ resource, ids, meta }) => Promise,
|
||||||
|
// createMany: ({ resource, variables, meta }) => Promise,
|
||||||
|
// deleteMany: ({ resource, ids, variables, meta }) => Promise,
|
||||||
|
// updateMany: ({ resource, ids, variables, meta }) => Promise,
|
||||||
|
// custom: ({ url, method, filters, sorters, payload, query, headers, meta }) => Promise,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default dataProvider;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export { default as authProvider } from "./auth-provider";
|
||||||
|
export * from "./auth-provider";
|
||||||
|
|
||||||
|
export { default as dataProvider } from "./data-provider";
|
||||||
|
export { default as liveProvider } from "./live-provider";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export * from "./utils";
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { LiveProvider } from "@refinedev/core";
|
||||||
|
import { watch } from "@ng-org/alien-deepsignals"
|
||||||
|
import { getSignalObject } from "./utils";
|
||||||
|
import { parsePatches } from "./utils";
|
||||||
|
import { LiveProviderConfig, UnwatchFn } from "./types";
|
||||||
|
|
||||||
|
const liveProvider = ({ dataModels }: LiveProviderConfig) : LiveProvider => ({
|
||||||
|
subscribe: async ({ channel, params, types, callback, meta }) : Promise<UnwatchFn> => {
|
||||||
|
console.log('subscribe', channel, params?.subscriptionType, params, types, meta);
|
||||||
|
|
||||||
|
const ids = params?.ids as string[];
|
||||||
|
|
||||||
|
if (channel.startsWith("resources/")) {
|
||||||
|
const resource = channel.split('/')[1];
|
||||||
|
|
||||||
|
if (dataModels[resource]) {
|
||||||
|
|
||||||
|
if (ids?.length > 0) {
|
||||||
|
// We don't close the subscription because we want to keep listening for changes
|
||||||
|
const set = await getSignalObject(dataModels[resource].shapeType, ids, false);
|
||||||
|
|
||||||
|
const { stopListening } = watch(set, ({ newValue, patches }) => {
|
||||||
|
console.log('object modified', newValue, patches);
|
||||||
|
|
||||||
|
const modifiedIds = parsePatches(patches);
|
||||||
|
|
||||||
|
for( const [graphId, eventType] of Object.entries(modifiedIds)) {
|
||||||
|
callback({
|
||||||
|
channel,
|
||||||
|
type: eventType,
|
||||||
|
payload: { ids: [graphId] },
|
||||||
|
date: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return stopListening;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// We don't close the subscription because we want to keep listening for changes
|
||||||
|
const set = await getSignalObject(dataModels[resource].shapeType, undefined, false);
|
||||||
|
|
||||||
|
console.log('set listening list', set);
|
||||||
|
|
||||||
|
const { stopListening } = watch(set, ({ newValue, patches }) => {
|
||||||
|
console.log('objects list modified', newValue, patches);
|
||||||
|
|
||||||
|
const modifiedIds = parsePatches(patches);
|
||||||
|
|
||||||
|
for( const [graphId, eventType] of Object.entries(modifiedIds)) {
|
||||||
|
callback({
|
||||||
|
channel,
|
||||||
|
type: eventType,
|
||||||
|
payload: { ids: [graphId] },
|
||||||
|
date: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return stopListening;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => undefined;
|
||||||
|
},
|
||||||
|
unsubscribe: async (unwatchFn: UnwatchFn | Promise<UnwatchFn>) => {
|
||||||
|
console.log('unsubscribe', typeof unwatchFn, unwatchFn);
|
||||||
|
const fn = await Promise.resolve(unwatchFn);
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default liveProvider;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { ShapeType } from "@ng-org/shex-orm";
|
||||||
|
import type { Session } from "@ng-org/web";
|
||||||
|
|
||||||
|
export type DataModel = {
|
||||||
|
shapeType: ShapeType<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DataModels = Record<string, DataModel>;
|
||||||
|
|
||||||
|
export type DataProviderConfig = {
|
||||||
|
dataModels: DataModels;
|
||||||
|
getSession: () => Session | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LiveProviderConfig = {
|
||||||
|
dataModels: DataModels;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UnwatchFn = () => void;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { BaseKey } from "@refinedev/core";
|
||||||
|
import { OrmSubscription } from "@ng-org/orm";
|
||||||
|
import type { ShapeType } from "@ng-org/shex-orm";
|
||||||
|
import { DeepSignalSet, DeepPatch } from "@ng-org/alien-deepsignals"
|
||||||
|
|
||||||
|
export const getSignalObject = async (shapeType: ShapeType<any>, ids?: string[], closeSubscription: boolean = true) : Promise<DeepSignalSet<any>> => {
|
||||||
|
const subscription = OrmSubscription.getOrCreate(shapeType, { graphs: ids || [''] });
|
||||||
|
await subscription.readyPromise;
|
||||||
|
const { signalObject } = subscription;
|
||||||
|
if (closeSubscription) subscription.close();
|
||||||
|
return signalObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isGraphKey = (key: BaseKey): boolean => {
|
||||||
|
return key !== null && typeof key === "string" && key.includes(":v:");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addIdToObject = (obj: any) => {
|
||||||
|
if (!obj["@graph"]) {
|
||||||
|
throw new Error("Object is missing @graph property");
|
||||||
|
}
|
||||||
|
return { id: obj["@graph"], ...obj };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parsePatches = (patches: DeepPatch[]) => {
|
||||||
|
let modifiedIds : Record<string, 'created' | 'updated' | 'deleted'> = {};
|
||||||
|
|
||||||
|
for( const patch of patches) {
|
||||||
|
// The path property include the @id property first, then the modified field
|
||||||
|
const [graphId] = (patch.path[0] as string).split('|');
|
||||||
|
const modifiedField = patch.path[1] as string;
|
||||||
|
|
||||||
|
// If a remove operation includes only the ID, this is a deletion
|
||||||
|
if (patch.op === "remove" && !modifiedField) {
|
||||||
|
modifiedIds[graphId] = 'deleted';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an add operation involves an @id, this is a creation
|
||||||
|
if (patch.op === "add" && modifiedField === "@id") {
|
||||||
|
if (modifiedIds[graphId] === 'deleted') {
|
||||||
|
// If the object was previously marked as deleted, consider this an update instead
|
||||||
|
modifiedIds[graphId] = 'updated';
|
||||||
|
} else {
|
||||||
|
modifiedIds[graphId] = 'created';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifiedIds[graphId] !== 'created' && modifiedIds[graphId] !== 'deleted') {
|
||||||
|
modifiedIds[graphId] = 'updated';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return modifiedIds;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": false,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declaration": true,
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"declaration": true,
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user