@manta-eu/horns-client 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/component-api.d.ts +7 -0
- package/dist/app/coverage-run.js +4 -3
- package/dist/app/home-page.js +6 -4
- package/dist/app/horn-page.js +7 -10
- package/dist/app/horn-studio.js +63 -10
- package/dist/app/horn-view-page.js +13 -9
- package/dist/app/horns-page.js +6 -9
- package/dist/app/new-horn-page.js +52 -10
- package/dist/app/paths.d.ts +15 -10
- package/dist/app/paths.js +18 -29
- package/dist/app/plans-page.d.ts +1 -0
- package/dist/app/plans-page.js +59 -0
- package/dist/app/reference-page.d.ts +1 -0
- package/dist/app/reference-page.js +55 -0
- package/dist/app/routes.js +7 -5
- package/dist/app/rule-page.js +18 -11
- package/dist/app/rules-page.js +12 -12
- package/dist/app/shape-entity-page.js +7 -9
- package/dist/app/shape-zoo-page.js +9 -10
- package/dist/app/shapes-page.js +13 -15
- package/dist/app/showcase-page.js +8 -11
- package/dist/components/app-shell.js +8 -7
- package/dist/components/catalog-card-view.js +4 -5
- package/dist/components/explain-dialog.d.ts +5 -0
- package/dist/components/explain-dialog.js +26 -0
- package/dist/components/plan-runs.js +2 -2
- package/dist/components/read-the-code.d.ts +19 -0
- package/dist/components/read-the-code.js +17 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/lib/horn-review.d.ts +7 -0
- package/dist/lib/horn-review.js +15 -0
- package/dist/lib/horn-variables.d.ts +8 -0
- package/dist/lib/horn-variables.js +39 -0
- package/dist/lib/manta/sign-in.d.ts +2 -0
- package/dist/lib/manta/sign-in.js +9 -0
- package/dist/lib/manta/sparql-select.d.ts +6 -2
- package/dist/lib/manta/sparql-select.js +17 -14
- package/dist/lib/reference/fake-sparql.d.ts +4 -0
- package/dist/lib/reference/fake-sparql.js +216 -0
- package/dist/lib/shapes/ldkit-source.d.ts +1 -1
- package/dist/lib/shapes/ldkit-source.js +1 -1
- package/dist/lib/studio-progress.js +3 -1
- package/dist/lib/surface/declarations.d.ts +5 -0
- package/dist/lib/surface/declarations.js +77 -0
- package/dist/lib/written-surface.js +13 -2
- package/dist/standalone/app.d.ts +2 -0
- package/dist/standalone/app.js +57 -0
- package/dist/standalone/client.d.ts +15 -0
- package/dist/{client.js → standalone/client.js} +12 -8
- package/dist/standalone/gates.d.ts +10 -0
- package/dist/standalone/gates.js +15 -0
- package/dist/styles.css +1 -1
- package/dist/vite/component-api.d.ts +8 -0
- package/dist/vite/component-api.js +24 -0
- package/dist/vite/index.d.ts +3 -0
- package/dist/vite/index.js +3 -0
- package/dist/vite/shape-catalog.d.ts +10 -0
- package/dist/vite/shape-catalog.js +35 -0
- package/dist/vite/typecheck-fs.d.ts +6 -0
- package/dist/vite/typecheck-fs.js +68 -0
- package/package.json +36 -12
- package/shape-catalog.d.ts +9 -0
- package/typecheck-fs.d.ts +6 -0
- package/dist/app/session-gate.d.ts +0 -6
- package/dist/app/session-gate.js +0 -19
- package/dist/client.d.ts +0 -10
- package/dist/lib/manta/providers.d.ts +0 -10
- package/dist/lib/manta/providers.js +0 -48
- package/dist/lib/manta/session.d.ts +0 -47
- package/dist/lib/manta/session.js +0 -204
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The module `hornsComponentApi` serves. A host picks up these types with
|
|
2
|
+
// `/// <reference types="@manta-eu/horns-client/component-api" />`, because a virtual module has no file for
|
|
3
|
+
// TypeScript to find on its own.
|
|
4
|
+
declare module "virtual:horns/component-api" {
|
|
5
|
+
/** One declaration per surfaced export, keyed by name, as the build read it out of the SDK. */
|
|
6
|
+
export const api: Readonly<Record<string, string>>;
|
|
7
|
+
}
|
package/dist/app/coverage-run.js
CHANGED
|
@@ -14,7 +14,7 @@ import { shapeZooPath } from "./paths";
|
|
|
14
14
|
import { isRecord } from "@manta-eu/horns";
|
|
15
15
|
import { readNdjson } from "@manta-eu/horns-server/shared";
|
|
16
16
|
import { problemFrom, problemOf } from "../lib/problem";
|
|
17
|
-
import {
|
|
17
|
+
import { useSparqlSelect } from "../lib/manta/sparql-select";
|
|
18
18
|
import { shapesRealm } from "../lib/shapes/catalog";
|
|
19
19
|
// The Studio designing a view, watched while it does it. A shape takes minutes and most of that is spent before
|
|
20
20
|
// the model says anything, so the run is shown as the four stages it actually goes through, each round the zoo
|
|
@@ -121,10 +121,11 @@ function Rounds({ rounds }) {
|
|
|
121
121
|
}
|
|
122
122
|
/** The card the Studio just designed, drawn on real instances — the thing the run is for. */
|
|
123
123
|
function DesignedCard({ datastoreId, spec }) {
|
|
124
|
+
const select = useSparqlSelect();
|
|
124
125
|
const realm = shapesRealm();
|
|
125
126
|
const zoo = useQuery({
|
|
126
127
|
queryKey: ["coverage-preview", datastoreId, spec.schema],
|
|
127
|
-
queryFn: ({ signal }) => buildShapeZoo({ spec, realm, datastoreId, select:
|
|
128
|
+
queryFn: ({ signal }) => buildShapeZoo({ spec, realm, datastoreId, select: select, signal }),
|
|
128
129
|
});
|
|
129
130
|
if (zoo.isPending)
|
|
130
131
|
return _jsx(Loading, { label: "Drawing it on real instances\u2026", rows: 3 });
|
|
@@ -146,7 +147,7 @@ function VerdictPanel({ datastoreId, run }) {
|
|
|
146
147
|
if (verdict === undefined)
|
|
147
148
|
return null;
|
|
148
149
|
return match(verdict.status)
|
|
149
|
-
.with("accepted", () => (_jsxs("div", { "data-verdict": "accepted", className: "flex flex-col gap-3", children: [_jsx("p", { className: "text-sm text-default-foreground", children: `The zoo accepted it in ${verdict.rounds ?? 1} round${verdict.rounds === 1 ? "" : "s"}.` }), isPresent(verdict.spec) ? (_jsxs(_Fragment, { children: [_jsx("ul", { className: "flex flex-wrap gap-1.5", children: verdict.spec.fields.map((field) => (_jsx("li", { "data-field": field.name, className: "rounded-md border border-default-border px-2 py-0.5 font-mono text-xs text-muted-foreground", children: `${field.name} · ${field.type}` }, field.name))) }), _jsx(DesignedCard, { datastoreId: datastoreId, spec: verdict.spec })] })) : null, isPresent(verdict.advice) ? (_jsxs("div", { "data-advice": true, className: "flex flex-col gap-2 rounded-lg border border-warning px-4 py-3", children: [_jsx("p", { className: "text-sm font-medium text-default-foreground", children: "What a stranger would not understand" }), _jsx("ul", { className: "flex list-disc flex-col gap-1 pl-5 text-sm text-muted-foreground", children: verdict.advice.map((one) => (_jsx("li", { children: one }, one))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "The zoo still accepted this view. This is advice for whoever reviews it, not a reason it failed." })] })) : null, _jsx(Link, { className: "text-sm text-muted-foreground underline hover:text-default-foreground", to: shapeZooPath(
|
|
150
|
+
.with("accepted", () => (_jsxs("div", { "data-verdict": "accepted", className: "flex flex-col gap-3", children: [_jsx("p", { className: "text-sm text-default-foreground", children: `The zoo accepted it in ${verdict.rounds ?? 1} round${verdict.rounds === 1 ? "" : "s"}.` }), isPresent(verdict.spec) ? (_jsxs(_Fragment, { children: [_jsx("ul", { className: "flex flex-wrap gap-1.5", children: verdict.spec.fields.map((field) => (_jsx("li", { "data-field": field.name, className: "rounded-md border border-default-border px-2 py-0.5 font-mono text-xs text-muted-foreground", children: `${field.name} · ${field.type}` }, field.name))) }), _jsx(DesignedCard, { datastoreId: datastoreId, spec: verdict.spec })] })) : null, isPresent(verdict.advice) ? (_jsxs("div", { "data-advice": true, className: "flex flex-col gap-2 rounded-lg border border-warning px-4 py-3", children: [_jsx("p", { className: "text-sm font-medium text-default-foreground", children: "What a stranger would not understand" }), _jsx("ul", { className: "flex list-disc flex-col gap-1 pl-5 text-sm text-muted-foreground", children: verdict.advice.map((one) => (_jsx("li", { children: one }, one))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "The zoo still accepted this view. This is advice for whoever reviews it, not a reason it failed." })] })) : null, _jsx(Link, { className: "text-sm text-muted-foreground underline hover:text-default-foreground", to: shapeZooPath(run.schema), children: "Open its zoo to keep it" })] })))
|
|
150
151
|
.with("gave-up", () => (_jsxs("div", { "data-verdict": "gave-up", className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm text-default-foreground", children: `It gave up after ${verdict.rounds ?? stages.length} round${verdict.rounds === 1 ? "" : "s"}. What the zoo kept saying:` }), _jsx("ul", { className: "flex list-disc flex-col gap-1 pl-5 text-sm text-muted-foreground", children: (verdict.problems ?? []).map((problem) => (_jsx("li", { children: problem }, problem))) })] })))
|
|
151
152
|
.with("no-data", () => (_jsx("p", { "data-verdict": "no-data", className: "text-sm text-muted-foreground", children: "Nothing to design from \u2014 this datastore holds no instances of it." })))
|
|
152
153
|
.otherwise(() => (_jsxs("p", { "data-verdict": "failed", className: "flex items-start gap-2 text-sm text-error-foreground", children: [_jsx(TriangleAlert, { size: 16, "aria-hidden": "true", className: "mt-0.5 shrink-0" }), verdict.error ?? "It broke, with no reason given."] })));
|
package/dist/app/home-page.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { isPresent } from "@manta-eu/lib/present";
|
|
3
|
-
import { Link } from "react-router";
|
|
4
3
|
import { ChevronRight, Database } from "lucide-react";
|
|
5
4
|
import { EmptyState } from "../components/states";
|
|
6
5
|
import { hornsPath } from "./paths";
|
|
7
|
-
import {
|
|
6
|
+
import { useAuthContext } from "@manta-eu/react/providers/AuthContext";
|
|
7
|
+
import { useActiveDatastore } from "@manta-eu/react/providers";
|
|
8
8
|
export function HomePage() {
|
|
9
|
-
const { datastores } =
|
|
10
|
-
|
|
9
|
+
const { datastores } = useAuthContext();
|
|
10
|
+
// Crossing between datastores is the router's move: the basename changes, which a <Link> cannot do.
|
|
11
|
+
const { enterDatastore } = useActiveDatastore();
|
|
12
|
+
return (_jsxs("div", { className: "flex w-full flex-col gap-6", children: [_jsxs("header", { className: "flex flex-col gap-1", children: [_jsx("h1", { className: "text-2xl font-semibold text-default-foreground", children: "Manta Horns" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Choose a datastore to see the Horns made from its data, or to make a new one." })] }), !isPresent(datastores) ? (_jsx(EmptyState, { title: "Your account has no datastores yet", hint: "A datastore is where your connected data lives. Ask the person who manages Manta at your company to give you access to one.", icon: _jsx(Database, { size: 20 }) })) : (_jsx("ul", { "data-datastores": true, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: datastores.map((datastore) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => enterDatastore(datastore.id, { path: hornsPath() }), className: "flex items-center gap-3 rounded-lg border border-default-border bg-card px-4 py-3 transition-colors hover:bg-card/70", children: [_jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-md bg-muted text-muted-foreground", children: _jsx(Database, { size: 16, "aria-hidden": "true" }) }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-default-foreground", children: datastore.name }), _jsx(ChevronRight, { size: 16, "aria-hidden": "true", className: "shrink-0 text-muted-foreground" })] }) }, datastore.id))) }))] }));
|
|
11
13
|
}
|
package/dist/app/horn-page.js
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { isPresent } from "@manta-eu/lib/present";
|
|
3
3
|
import { useParams, useSearchParams } from "react-router";
|
|
4
|
+
import { useActiveDatastore } from "@manta-eu/react/providers";
|
|
4
5
|
import { variablesFromSearch } from "@manta-eu/horns/render";
|
|
5
6
|
import { BackLink, DatastoreMissing, ErrorState, Loading } from "../components/states";
|
|
6
7
|
import { hornsPath } from "./paths";
|
|
7
8
|
import { HornStudio } from "./horn-studio";
|
|
8
9
|
import { useSavedHorn } from "./saved-horn";
|
|
9
10
|
import { VariableBar } from "./variable-bar";
|
|
10
|
-
import { MantaSparqlProvider } from "../lib/manta/providers";
|
|
11
|
-
import { useMantaSession } from "../lib/manta/session";
|
|
12
11
|
// A saved Horn, as the Studio's whole screen: the document comes back from the datastore and the snapshot is
|
|
13
12
|
// built from it, so what is on screen is what is stored rather than what a client happened to be holding.
|
|
14
13
|
export function HornPage() {
|
|
15
|
-
const { datastoreId
|
|
16
|
-
const {
|
|
17
|
-
|
|
18
|
-
if (!isPresent(datastore)) {
|
|
14
|
+
const { datastoreId } = useActiveDatastore();
|
|
15
|
+
const { slug } = useParams();
|
|
16
|
+
if (!isPresent(datastoreId)) {
|
|
19
17
|
return (_jsx(Centered, { children: _jsx(DatastoreMissing, {}) }));
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
return (_jsx(MantaSparqlProvider, { datastoreId: datastore.id, children: _jsx(SavedHornPage, { slug: slug ?? "", datastoreId: datastore.id }) }));
|
|
19
|
+
return _jsx(SavedHornPage, { slug: slug ?? "", datastoreId: datastoreId });
|
|
23
20
|
}
|
|
24
21
|
/** Until there is a Horn to put in the Studio, the screen is just this. */
|
|
25
22
|
function Centered({ children }) {
|
|
@@ -29,10 +26,10 @@ function SavedHornPage({ datastoreId, slug }) {
|
|
|
29
26
|
const [search] = useSearchParams();
|
|
30
27
|
const horn = useSavedHorn(datastoreId, slug);
|
|
31
28
|
if (horn.isError) {
|
|
32
|
-
return (_jsxs(Centered, { children: [_jsx(BackLink, { to: hornsPath(
|
|
29
|
+
return (_jsxs(Centered, { children: [_jsx(BackLink, { to: hornsPath(), label: "All Horns" }), _jsx(ErrorState, { title: "That Horn could not be opened", hint: horn.error instanceof Error ? horn.error.message : "It may have been removed." })] }));
|
|
33
30
|
}
|
|
34
31
|
if (!isPresent(horn.data)) {
|
|
35
32
|
return (_jsx(Centered, { children: _jsx(Loading, { label: "Opening the Horn\u2026" }) }));
|
|
36
33
|
}
|
|
37
|
-
return (_jsx(HornStudio, { datastoreId: datastoreId, slug: slug, stored: horn.data.horn, revision: horn.data.revision, backTo: hornsPath(
|
|
34
|
+
return (_jsx(HornStudio, { datastoreId: datastoreId, slug: slug, stored: horn.data.horn, revision: horn.data.revision, backTo: hornsPath(), variables: variablesFromSearch(horn.data.horn, search.toString()), toolbar: _jsx(VariableBar, { horn: horn.data.horn }) }));
|
|
38
35
|
}
|
package/dist/app/horn-studio.js
CHANGED
|
@@ -11,10 +11,10 @@ import { describeHornChange, minViewWidth, visualEditOps } from "@manta-eu/horns
|
|
|
11
11
|
import { HornSnapshotView } from "@manta-eu/horns-kit";
|
|
12
12
|
import { writtenHornView } from "../components/written-horn-view";
|
|
13
13
|
import { catalogCardView } from "../components/catalog-card-view";
|
|
14
|
-
import { ReadTheCode, writtenReviewStates } from "../components/read-the-code";
|
|
14
|
+
import { ReadTheCode, ReadTheCodeDialog, writtenReviewStates } from "../components/read-the-code";
|
|
15
15
|
import { PlanRunDialog, usePlanRuns } from "../components/plan-runs";
|
|
16
16
|
import { sendHornScreenshot } from "../components/rasterize";
|
|
17
|
-
import { reviewHorn } from "../lib/horn-review";
|
|
17
|
+
import { nameOfView, reviewHorn } from "../lib/horn-review";
|
|
18
18
|
import { StudioChat } from "../components/studio-chat";
|
|
19
19
|
import { ErrorState, Loading } from "../components/states";
|
|
20
20
|
import { isRecord } from "@manta-eu/horns";
|
|
@@ -42,12 +42,13 @@ function failureOf(body, status) {
|
|
|
42
42
|
}
|
|
43
43
|
/** How much a card grows or shrinks by: a third of the twelve columns, so the page keeps its rhythm. */
|
|
44
44
|
const widthStep = 4;
|
|
45
|
+
// Trimmed after the cut, not before it: a slug ending in a dash is not one the schema accepts.
|
|
45
46
|
const kebab = (name) => name
|
|
46
47
|
.trim()
|
|
47
48
|
.toLowerCase()
|
|
48
49
|
.replace(/[^a-z0-9]+/g, "-")
|
|
49
|
-
.
|
|
50
|
-
.
|
|
50
|
+
.slice(0, 60)
|
|
51
|
+
.replace(/^-+|-+$/g, "");
|
|
51
52
|
/**
|
|
52
53
|
* The Studio as a whole screen, the way Lovable lays out a project: the conversation down the left, the Horn in
|
|
53
54
|
* a frame on the right, and saving in the bar across the top.
|
|
@@ -87,6 +88,10 @@ export function HornStudio(props) {
|
|
|
87
88
|
// Hashing is async, so the states are held rather than derived: a card shows what was last worked out for
|
|
88
89
|
// the draft it is drawing.
|
|
89
90
|
const [reviewStates, setReviewStates] = useState({});
|
|
91
|
+
// Save does not refuse a Horn whose code nobody read; it offers the reading, then goes on saving.
|
|
92
|
+
const [toRead, setToRead] = useState([]);
|
|
93
|
+
const [savingAfterReading, setSavingAfterReading] = useState(false);
|
|
94
|
+
const [readingTotal, setReadingTotal] = useState(0);
|
|
90
95
|
useEffect(() => {
|
|
91
96
|
let live = true;
|
|
92
97
|
void writtenReviewStates(shown.horn).then((states) => {
|
|
@@ -182,7 +187,8 @@ export function HornStudio(props) {
|
|
|
182
187
|
setProgress([...thinking]);
|
|
183
188
|
review = await reviewHorn({ datastoreId, horn: reply.horn, asked, variables });
|
|
184
189
|
for (const problem of review.problems) {
|
|
185
|
-
|
|
190
|
+
const named = nameOfView(reply.horn, problem.viewId);
|
|
191
|
+
thinking.push({ title: `“${named}” would not draw`, detail: problem.message });
|
|
186
192
|
}
|
|
187
193
|
if (isPresent(review.advice)) {
|
|
188
194
|
const count = review.advice.length;
|
|
@@ -198,7 +204,7 @@ export function HornStudio(props) {
|
|
|
198
204
|
}
|
|
199
205
|
const said = [
|
|
200
206
|
// A view that would not draw is not an opinion; it goes first, and it is a fact the Studio can act on.
|
|
201
|
-
...review.problems.map((problem) => `“${problem.viewId}” does not draw: ${problem.message}`),
|
|
207
|
+
...review.problems.map((problem) => `“${nameOfView(reply.horn, problem.viewId)}” does not draw: ${problem.message}`),
|
|
202
208
|
...review.advice,
|
|
203
209
|
];
|
|
204
210
|
setEntries([
|
|
@@ -350,14 +356,37 @@ export function HornStudio(props) {
|
|
|
350
356
|
setSaveFailure("Give the Horn a title before saving it: its address is made from the title.");
|
|
351
357
|
return;
|
|
352
358
|
}
|
|
359
|
+
// Asked of the Horn being saved rather than the one on screen, which may be an earlier version. The
|
|
360
|
+
// save route refuses unread code, so meeting it here turns a dead end into the reading it asks for.
|
|
361
|
+
const states = await writtenReviewStates(saving.horn);
|
|
362
|
+
const unread = saving.horn.views
|
|
363
|
+
.map((view) => view.id)
|
|
364
|
+
.filter((id) => states[id] === "unread" || states[id] === "stale");
|
|
365
|
+
if (unread.length > 0) {
|
|
366
|
+
setSaveFailure(undefined);
|
|
367
|
+
setToRead(unread);
|
|
368
|
+
setReadingTotal(unread.length);
|
|
369
|
+
setSavingAfterReading(true);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
353
372
|
setSaveState("saving");
|
|
354
373
|
setSaveFailure(undefined);
|
|
355
374
|
try {
|
|
356
|
-
const
|
|
375
|
+
const put = (at) => fetch(`/api/d/${encodeURIComponent(datastoreId)}/horns/${encodeURIComponent(at)}`, {
|
|
357
376
|
method: "PUT",
|
|
358
377
|
headers: { "content-type": "application/json" },
|
|
359
378
|
body: JSON.stringify({ horn: saving.horn, ...(isPresent(baseRevision) ? { baseRevision } : {}) }),
|
|
360
379
|
});
|
|
380
|
+
// A Horn that has never been saved takes its address from its title, and two Horns can be given the
|
|
381
|
+
// same title — every Horn built for a plan nobody named is "Suggested changes". The name is taken, not
|
|
382
|
+
// the Horn: saving under the next one free is what a person means, and a 409 here can mean nothing
|
|
383
|
+
// else, because revising a Horn is what carries a `baseRevision`.
|
|
384
|
+
let at = name;
|
|
385
|
+
let response = await put(at);
|
|
386
|
+
for (let next = 2; response.status === 409 && !isPresent(baseRevision) && next <= 20; next += 1) {
|
|
387
|
+
at = `${name}-${next}`;
|
|
388
|
+
response = await put(at);
|
|
389
|
+
}
|
|
361
390
|
const body = await response.json().catch(() => undefined);
|
|
362
391
|
if (!response.ok) {
|
|
363
392
|
setSaveState("failed");
|
|
@@ -365,13 +394,13 @@ export function HornStudio(props) {
|
|
|
365
394
|
return;
|
|
366
395
|
}
|
|
367
396
|
setBaseRevision(isRecord(body) && typeof body["revision"] === "string" ? body["revision"] : baseRevision);
|
|
368
|
-
setSlug(
|
|
397
|
+
setSlug(at);
|
|
369
398
|
setSavedId(saving.id);
|
|
370
399
|
setSaveState("idle");
|
|
371
400
|
// The showcase leads with a picture of the Horn, and this browser has it drawn already. A saved Horn is
|
|
372
401
|
// saved whether or not the picture was taken, so this cannot fail the save and is never waited on.
|
|
373
402
|
if (isPresent(snapshot.data)) {
|
|
374
|
-
void sendHornScreenshot({ datastoreId, slug:
|
|
403
|
+
void sendHornScreenshot({ datastoreId, slug: at, snapshot: snapshot.data });
|
|
375
404
|
}
|
|
376
405
|
}
|
|
377
406
|
catch (caught) {
|
|
@@ -379,6 +408,13 @@ export function HornStudio(props) {
|
|
|
379
408
|
setSaveFailure(caught instanceof Error ? caught.message : String(caught));
|
|
380
409
|
}
|
|
381
410
|
}
|
|
411
|
+
useEffect(() => {
|
|
412
|
+
if (!savingAfterReading || toRead.length > 0)
|
|
413
|
+
return;
|
|
414
|
+
setSavingAfterReading(false);
|
|
415
|
+
// The queue emptying is the event; `save` is redefined every render and is not one.
|
|
416
|
+
void save();
|
|
417
|
+
}, [savingAfterReading, toRead]);
|
|
382
418
|
// Where things stand, as a sentence: a badge says less, and an address like "/spend-watch" says nothing.
|
|
383
419
|
const status = saveState === "saving"
|
|
384
420
|
? "Saving…"
|
|
@@ -391,7 +427,24 @@ export function HornStudio(props) {
|
|
|
391
427
|
: "Not saved yet";
|
|
392
428
|
const showChat = !mobile || pane === "chat";
|
|
393
429
|
const showPreview = !mobile || pane === "preview";
|
|
394
|
-
|
|
430
|
+
const reading = (() => {
|
|
431
|
+
const [first] = toRead;
|
|
432
|
+
if (first === undefined)
|
|
433
|
+
return undefined;
|
|
434
|
+
const view = latest.horn.views.find((candidate) => candidate.id === first);
|
|
435
|
+
if (view === undefined || "ref" in view || view.render.kind !== "tsx")
|
|
436
|
+
return undefined;
|
|
437
|
+
return { id: first, title: view.title, source: view.render.source, state: reviewStates[first] ?? "unread" };
|
|
438
|
+
})();
|
|
439
|
+
return (_jsxs("div", { "data-horn-studio": true, className: "flex h-svh w-full flex-col bg-sidebar", children: [reading === undefined ? null : (_jsx(ReadTheCodeDialog, { viewId: reading.id, title: reading.title, source: reading.source, state: reading.state, open: true, standing: { at: readingTotal - toRead.length + 1, of: readingTotal }, onClose: () => {
|
|
440
|
+
// "Not yet" stops the whole save, rather than moving on to the next one unread.
|
|
441
|
+
setToRead([]);
|
|
442
|
+
setSavingAfterReading(false);
|
|
443
|
+
}, onKeep: (change) => {
|
|
444
|
+
// Only once the keep is in the draft: the save that follows the last one reads the draft, and
|
|
445
|
+
// a draft still missing it looks exactly like code nobody read.
|
|
446
|
+
void edit(change, `Read and kept the code for "${reading.title}".`).then(() => setToRead((was) => was.slice(1)));
|
|
447
|
+
} })), _jsxs("header", { className: "flex h-14 shrink-0 items-center gap-3 px-3", children: [mobile ? (_jsx(Button, { variant: "ghost", size: "sm", iconOnly: true, icon: ArrowLeft, "aria-label": "All Horns", onClick: () => navigate(backTo) })) : (_jsx(Button, { variant: "ghost", size: "sm", icon: ArrowLeft, showLeftIcon: true, onClick: () => navigate(backTo), children: "All Horns" })), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium text-default-foreground", children: latest.horn.title }), _jsx("span", { "data-save-status": true, className: "truncate text-xs text-muted-foreground", children: status })] }), mobile ? (_jsx("div", { className: "flex items-center gap-1 rounded-full bg-muted p-1", role: "group", "aria-label": "Show", children: ["chat", "preview"].map((each) => (_jsx(Button, { variant: pane === each ? "secondary" : "ghost", size: "xs", "aria-pressed": pane === each, onClick: () => setPane(each), children: each === "chat" ? "Chat" : "Preview" }, each))) })) : null, _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [unsaved ? (_jsx(Button, { variant: "secondary", size: "sm", disabled: saveState === "saving" || pending, onClick: discard, children: mobile ? "Discard" : "Discard changes" })) : null, _jsx(Button, { variant: "primary", size: "sm", disabled: !unsaved || saveState === "saving" || pending, onClick: () => void save(), children: saveState === "saving" ? "Saving…" : mobile ? "Save" : "Save changes" })] })] }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [showChat ? (_jsxs("aside", { ref: panelRef, "aria-label": "Studio chat", className: "flex min-h-0 shrink-0 flex-col", style: mobile ? { width: "100%" } : { width: panelWidth }, children: [isPresent(saveFailure) ? (_jsx("div", { className: "px-4 pt-2", children: _jsx(ErrorState, { title: "That save did not go through", hint: saveFailure }) })) : null, _jsx(StudioChat, { entries: entries, pending: pending, progress: progress, previewing: shown.id, welcome: welcome ?? "What would you like to change?", reassurance: reassurance ?? "Say it in your own words. Nothing is saved until you choose Save changes.", starters: starters ?? restyleStarters, placeholder: placeholder ?? "Describe a change, in your own words…", onSend: (text) => void send(text), onPreview: (id) => {
|
|
395
448
|
setViewing(id === latest.id ? undefined : id);
|
|
396
449
|
if (mobile)
|
|
397
450
|
setPane("preview");
|
|
@@ -3,6 +3,7 @@ import { useState } from "react";
|
|
|
3
3
|
import { useQuery } from "@tanstack/react-query";
|
|
4
4
|
import { isPresent } from "@manta-eu/lib/present";
|
|
5
5
|
import { Link, useParams, useSearchParams } from "react-router";
|
|
6
|
+
import { useActiveDatastore } from "@manta-eu/react/providers";
|
|
6
7
|
import { Camera, Link2, Pencil } from "lucide-react";
|
|
7
8
|
import { Button, Input } from "@manta-eu/react";
|
|
8
9
|
import { hornSnapshotSchema, variablesFromSearch } from "@manta-eu/horns/render";
|
|
@@ -11,24 +12,23 @@ import { writtenHornView } from "../components/written-horn-view";
|
|
|
11
12
|
import { catalogCardView } from "../components/catalog-card-view";
|
|
12
13
|
import { PlanRunDialog, usePlanRuns } from "../components/plan-runs";
|
|
13
14
|
import { sendHornScreenshot } from "../components/rasterize";
|
|
15
|
+
import { useExplainDialog } from "../components/explain-dialog";
|
|
16
|
+
import { useHornVariables } from "../lib/horn-variables";
|
|
14
17
|
import { DatastoreMissing, ErrorState, Loading } from "../components/states";
|
|
15
18
|
import { hornPath, hornsPath } from "./paths";
|
|
16
19
|
import { useSavedHorn } from "./saved-horn";
|
|
17
20
|
import { VariableBar } from "./variable-bar";
|
|
18
21
|
import { isRecord } from "@manta-eu/horns";
|
|
19
|
-
import { MantaSparqlProvider } from "../lib/manta/providers";
|
|
20
|
-
import { useMantaSession } from "../lib/manta/session";
|
|
21
22
|
import { appTheme } from "../lib/theme";
|
|
22
23
|
// A saved Horn as the thing itself: what a shared link opens. No chat, nothing to save — the URL is the state,
|
|
23
24
|
// so the link somebody is sent draws exactly the Horn they were looking at, filters and all. The plans behind a
|
|
24
25
|
// view are still offered here, because a person who can open the Horn is a person who can act on it.
|
|
25
26
|
export function HornViewPage() {
|
|
26
|
-
const { datastoreId
|
|
27
|
-
const {
|
|
28
|
-
|
|
29
|
-
if (!isPresent(datastore))
|
|
27
|
+
const { datastoreId } = useActiveDatastore();
|
|
28
|
+
const { slug } = useParams();
|
|
29
|
+
if (!isPresent(datastoreId))
|
|
30
30
|
return _jsx(DatastoreMissing, {});
|
|
31
|
-
return
|
|
31
|
+
return _jsx(Viewer, { datastoreId: datastoreId, slug: slug ?? "" });
|
|
32
32
|
}
|
|
33
33
|
function ShareLink() {
|
|
34
34
|
const [shown, setShown] = useState();
|
|
@@ -60,10 +60,14 @@ function UpdatePicture({ datastoreId, slug, snapshot }) {
|
|
|
60
60
|
return (_jsxs(_Fragment, { children: [problem === undefined ? null : (_jsx("span", { role: "alert", className: "text-xs text-muted-foreground", children: problem })), _jsxs(Button, { variant: "secondary", size: "sm", disabled: state === "working", onClick: () => void take(), children: [_jsx(Camera, { size: 14, "aria-hidden": "true" }), state === "working" ? "Taking…" : state === "kept" ? "Picture updated" : "Update picture"] })] }));
|
|
61
61
|
}
|
|
62
62
|
function Viewer({ datastoreId, slug }) {
|
|
63
|
+
const explain = useExplainDialog();
|
|
63
64
|
const [search] = useSearchParams();
|
|
64
65
|
const saved = useSavedHorn(datastoreId, slug);
|
|
65
66
|
const horn = saved.data?.horn;
|
|
66
67
|
const variables = horn ? variablesFromSearch(horn, search.toString()) : {};
|
|
68
|
+
// Writing goes through nuqs, which owns the URL; reading stays with `variablesFromSearch`, which is what
|
|
69
|
+
// types a value against the variable that declared it.
|
|
70
|
+
const urlVariables = useHornVariables(horn);
|
|
67
71
|
const snapshot = useQuery({
|
|
68
72
|
enabled: isPresent(horn),
|
|
69
73
|
queryKey: ["horn-view", datastoreId, slug, saved.data?.revision ?? "", variables],
|
|
@@ -90,7 +94,7 @@ function Viewer({ datastoreId, slug }) {
|
|
|
90
94
|
}
|
|
91
95
|
if (!isPresent(horn))
|
|
92
96
|
return _jsx(Loading, { label: "Opening the Horn\u2026", rows: 3 });
|
|
93
|
-
return (_jsxs("div", { "data-horn-viewer": true, className: "flex w-full flex-col gap-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(Link, { to: hornsPath(
|
|
97
|
+
return (_jsxs("div", { "data-horn-viewer": true, className: "flex w-full flex-col gap-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(Link, { to: hornsPath(), className: "text-sm text-muted-foreground hover:text-default-foreground", children: "All Horns" }), _jsx(VariableBar, { horn: horn })] }), _jsxs("div", { className: "flex items-center gap-2", children: [planRuns.problem === undefined ? null : (_jsx("span", { role: "alert", className: "text-xs text-muted-foreground", children: planRuns.problem })), _jsx(UpdatePicture, { datastoreId: datastoreId, slug: slug, snapshot: snapshot.data }), _jsx(ShareLink, {}), _jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsxs(Link, { to: hornPath(slug), children: [_jsx(Pencil, { size: 14, "aria-hidden": "true" }), "Change it"] }) })] })] }), snapshot.isError ? (_jsx(ErrorState, { title: "The Horn could not be drawn", hint: snapshot.error.message })) : !isPresent(snapshot.data) ? (_jsx(Loading, { label: "Drawing the Horn\u2026", rows: 4 })) : (
|
|
94
98
|
// The Horn draws its own page, title and canvas.
|
|
95
|
-
_jsxs("div", { "data-horn-preview": true, className: "w-full overflow-hidden rounded-xl", children: [_jsx(HornSnapshotView, { snapshot: snapshot.data, mode: "page", theme: appTheme(), planActions: planRuns.actions, writtenView: writtenHornView, catalogCard: catalogCardView }), _jsx(PlanRunDialog, { runs: planRuns, labels: snapshot.data.labels })] }))] }));
|
|
99
|
+
_jsxs("div", { "data-horn-preview": true, className: "w-full overflow-hidden rounded-xl", children: [_jsx(HornSnapshotView, { snapshot: snapshot.data, mode: "page", theme: appTheme(), planActions: planRuns.actions, writtenView: writtenHornView, catalogCard: catalogCardView, explain: explain.open, onVariableChange: urlVariables.set }), _jsx(PlanRunDialog, { runs: planRuns, labels: snapshot.data.labels }), explain.dialog] }))] }));
|
|
96
100
|
}
|
package/dist/app/horns-page.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { isPresent } from "@manta-eu/lib/present";
|
|
3
|
-
import { Link
|
|
3
|
+
import { Link } from "react-router";
|
|
4
|
+
import { useActiveDatastore } from "@manta-eu/react/providers";
|
|
4
5
|
import { ChevronRight, Columns3, Plus } from "lucide-react";
|
|
5
6
|
import { Button } from "@manta-eu/react";
|
|
6
7
|
import { PageHeader } from "../components/page-header";
|
|
@@ -8,15 +9,11 @@ import { DatastoreMissing, EmptyState, ErrorState, Loading } from "../components
|
|
|
8
9
|
import { hornViewPath, newHornPath } from "./paths";
|
|
9
10
|
import { useQuery } from "@tanstack/react-query";
|
|
10
11
|
import { isRecord } from "@manta-eu/horns";
|
|
11
|
-
import { MantaSparqlProvider } from "../lib/manta/providers";
|
|
12
|
-
import { useMantaSession } from "../lib/manta/session";
|
|
13
12
|
export function HornsPage() {
|
|
14
|
-
const { datastoreId } =
|
|
15
|
-
|
|
16
|
-
const datastore = datastores.find((candidate) => candidate.id === datastoreId);
|
|
17
|
-
if (!isPresent(datastore))
|
|
13
|
+
const { datastoreId } = useActiveDatastore();
|
|
14
|
+
if (!isPresent(datastoreId))
|
|
18
15
|
return _jsx(DatastoreMissing, {});
|
|
19
|
-
return
|
|
16
|
+
return _jsx(HornsIn, { datastoreId: datastoreId });
|
|
20
17
|
}
|
|
21
18
|
function HornsIn({ datastoreId }) {
|
|
22
19
|
const horns = useQuery({
|
|
@@ -39,5 +36,5 @@ function HornsIn({ datastoreId }) {
|
|
|
39
36
|
: []);
|
|
40
37
|
},
|
|
41
38
|
});
|
|
42
|
-
return (_jsxs("div", { className: "flex w-full flex-col gap-6", children: [_jsx(PageHeader, { title: "Horns", description: "Dashboards and apps made from your data. Open one to look at it or change it, or describe a new one.", actions: _jsx(Button, { variant: "primary", asChild: true, children: _jsxs(Link, { to: newHornPath(
|
|
39
|
+
return (_jsxs("div", { className: "flex w-full flex-col gap-6", children: [_jsx(PageHeader, { title: "Horns", description: "Dashboards and apps made from your data. Open one to look at it or change it, or describe a new one.", actions: _jsx(Button, { variant: "primary", asChild: true, children: _jsxs(Link, { to: newHornPath(), children: [_jsx(Plus, { size: 16, "aria-hidden": "true" }), "New Horn"] }) }) }), horns.isPending ? (_jsx(Loading, { label: "Reading this datastore's Horns\u2026" })) : horns.isError ? (_jsx(ErrorState, { title: "Your Horns could not be loaded", hint: "Try again in a moment. If it keeps happening, the data service may be down." })) : !isPresent(horns.data) ? (_jsx(EmptyState, { title: "No Horns yet", hint: "Describe what you want to see, in your own words, and it is built for you from this datastore's data.", icon: _jsx(Columns3, { size: 20 }), action: _jsx(Button, { variant: "primary", size: "sm", asChild: true, children: _jsxs(Link, { to: newHornPath(), children: [_jsx(Plus, { size: 14, "aria-hidden": "true" }), "Make your first Horn"] }) }) })) : (_jsx("ul", { "data-horns": true, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: horns.data.map((horn) => (_jsx("li", { children: _jsxs(Link, { to: hornViewPath(horn.slug), className: "flex items-center gap-3 rounded-lg border border-default-border bg-card px-4 py-3 transition-colors hover:bg-card/70", children: [_jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-md bg-muted text-muted-foreground", children: _jsx(Columns3, { size: 16 }) }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium text-default-foreground", children: horn.title }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: horn.number === 1 ? "Saved once" : `Saved ${horn.number} times` })] }), _jsx(ChevronRight, { size: 16, "aria-hidden": "true", className: "shrink-0 text-muted-foreground" })] }) }, horn.slug))) }))] }));
|
|
43
40
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useSearchParams } from "react-router";
|
|
5
|
+
import { z } from "zod";
|
|
3
6
|
import { isPresent } from "@manta-eu/lib/present";
|
|
4
|
-
import {
|
|
7
|
+
import { useActiveDatastore } from "@manta-eu/react/providers";
|
|
8
|
+
import { hornSchema } from "@manta-eu/horns";
|
|
5
9
|
import { standaloneHorn } from "@manta-eu/horns/shapes";
|
|
6
|
-
import { DatastoreMissing } from "../components/states";
|
|
10
|
+
import { DatastoreMissing, ErrorState, Loading } from "../components/states";
|
|
7
11
|
import { hornsPath } from "./paths";
|
|
8
12
|
import { HornStudio } from "./horn-studio";
|
|
9
|
-
import {
|
|
10
|
-
import { useMantaSession } from "../lib/manta/session";
|
|
13
|
+
import { problemFrom } from "../lib/problem";
|
|
11
14
|
import { shapesRealm } from "../lib/shapes/catalog";
|
|
12
15
|
// The way in: a Horn from nothing. It opens straight into the Studio with an empty document, the way a new
|
|
13
16
|
// Lovable project opens on its composer: the first thing typed is what it should show, the model names it as
|
|
@@ -18,17 +21,56 @@ const starterPrompts = [
|
|
|
18
21
|
"What happened in search last week that I should look at?",
|
|
19
22
|
];
|
|
20
23
|
export function NewHornPage() {
|
|
21
|
-
const { datastoreId } =
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
if (!isPresent(
|
|
24
|
+
const { datastoreId } = useActiveDatastore();
|
|
25
|
+
const [search] = useSearchParams();
|
|
26
|
+
const about = search.get("about");
|
|
27
|
+
if (!isPresent(datastoreId)) {
|
|
25
28
|
return (_jsx("main", { className: "flex h-svh w-full items-center justify-center bg-sidebar p-6", children: _jsx(DatastoreMissing, {}) }));
|
|
26
29
|
}
|
|
27
|
-
return (_jsx(
|
|
30
|
+
return isPresent(about) ? (_jsx(NewHornAbout, { datastoreId: datastoreId, about: about })) : (_jsx(NewHorn, { datastoreId: datastoreId }));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* What the lookup answers with: the Horn, and whether it is one somebody kept. A kept Horn brings its slug
|
|
34
|
+
* and the revision it is at, which is what saving an edit of it needs — without them a save would write a
|
|
35
|
+
* second Horn about the same thing, or be refused for revising a Horn it never read.
|
|
36
|
+
*/
|
|
37
|
+
const aboutAnswer = z.object({
|
|
38
|
+
horn: hornSchema,
|
|
39
|
+
kept: z.boolean(),
|
|
40
|
+
slug: z.string().min(1).optional(),
|
|
41
|
+
revision: z.string().min(1).optional(),
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* A Horn started from the thing it is about, rather than from nothing.
|
|
45
|
+
*
|
|
46
|
+
* The worker already builds one for a rule or a plan that nobody has kept — that is what the route answers
|
|
47
|
+
* with — so this opens the Studio on it. Still a draft: it becomes the Horn for that thing when somebody
|
|
48
|
+
* saves it, and `about` is what makes the next lookup find it. One somebody already kept opens as itself,
|
|
49
|
+
* so that saving revises it rather than making another.
|
|
50
|
+
*/
|
|
51
|
+
function NewHornAbout({ datastoreId, about }) {
|
|
52
|
+
const seed = useQuery({
|
|
53
|
+
queryKey: ["horn-about", datastoreId, about],
|
|
54
|
+
queryFn: async ({ signal }) => {
|
|
55
|
+
const at = `/api/d/${encodeURIComponent(datastoreId)}/horns/by/about?iri=${encodeURIComponent(about)}`;
|
|
56
|
+
const response = await fetch(at, { signal });
|
|
57
|
+
if (!response.ok)
|
|
58
|
+
throw new Error(await problemFrom(response, `The answer was ${response.status}.`));
|
|
59
|
+
return aboutAnswer.parse(await response.json());
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (seed.isPending) {
|
|
63
|
+
return (_jsx("main", { className: "flex h-svh w-full items-center justify-center bg-sidebar p-6", children: _jsx(Loading, { label: "Building a Horn for this\u2026" }) }));
|
|
64
|
+
}
|
|
65
|
+
if (seed.isError) {
|
|
66
|
+
return (_jsx("main", { className: "flex h-svh w-full items-center justify-center bg-sidebar p-6", children: _jsx(ErrorState, { title: "Nothing could be built for this", hint: seed.error.message }) }));
|
|
67
|
+
}
|
|
68
|
+
const { horn, kept, slug, revision } = seed.data;
|
|
69
|
+
return (_jsx(HornStudio, { datastoreId: datastoreId, stored: horn, ...(isPresent(slug) ? { slug } : {}), ...(isPresent(revision) ? { revision } : {}), backTo: hornsPath(), welcome: "Here is what this shows. What would you change?", reassurance: kept ? "Saving keeps your changes to this Horn." : "Nothing is saved until you save it.", placeholder: "Describe what you want to see\u2026" }));
|
|
28
70
|
}
|
|
29
71
|
function NewHorn({ datastoreId }) {
|
|
30
72
|
// Made once: the route owns the IRI and the revision on save, and this is a document that has never been
|
|
31
73
|
// stored. A new render must not make a new one underneath the draft.
|
|
32
74
|
const [horn] = useState(() => standaloneHorn({ id: "untitled", title: "Untitled Horn", views: [], realm: shapesRealm(), datastoreId }));
|
|
33
|
-
return (_jsx(HornStudio, { datastoreId: datastoreId, stored: horn, backTo: hornsPath(
|
|
75
|
+
return (_jsx(HornStudio, { datastoreId: datastoreId, stored: horn, backTo: hornsPath(), welcome: "What should this Horn show?", reassurance: "Say it in your own words, and I will build it from this datastore's data.", placeholder: "Describe what you want to see\u2026", starters: starterPrompts }));
|
|
34
76
|
}
|
package/dist/app/paths.d.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
export declare
|
|
2
|
-
export declare
|
|
3
|
-
|
|
1
|
+
export declare const hornsPath: () => string;
|
|
2
|
+
export declare const newHornPath: () => string;
|
|
3
|
+
/** A new Horn started from the thing it is about: a rule, an action plan. The IRI rides as a parameter. */
|
|
4
|
+
export declare const newHornAboutPath: (iri: string) => string;
|
|
5
|
+
export declare const hornPath: (slug: string) => string;
|
|
4
6
|
/** A saved Horn as the thing itself: what a shared link opens, with the variables it carries. */
|
|
5
|
-
export declare
|
|
6
|
-
export declare function shapesPath(datastoreId: string): string;
|
|
7
|
-
export declare function shapeZooPath(datastoreId: string, schema: string): string;
|
|
8
|
-
export declare function shapeEntityPath(datastoreId: string, schema: string, entity: string): string;
|
|
7
|
+
export declare const hornViewPath: (slug: string) => string;
|
|
9
8
|
/** A datastore's Horns as cards: what each one answers, who it is for, what it reads. */
|
|
10
|
-
export declare const showcasePath: (
|
|
9
|
+
export declare const showcasePath: () => string;
|
|
11
10
|
/** What Manta works out about a datastore: every rule, and whether it can be looked at. */
|
|
12
|
-
export declare
|
|
13
|
-
export declare
|
|
11
|
+
export declare const rulesPath: () => string;
|
|
12
|
+
export declare const plansPath: () => string;
|
|
13
|
+
export declare const rulePath: (ruleId: string) => string;
|
|
14
|
+
/** The reference Horn drawn against a fixture. Dev only; see docs/standard.md. */
|
|
15
|
+
export declare const referencePath: () => string;
|
|
16
|
+
export declare const shapesPath: () => string;
|
|
17
|
+
export declare const shapeZooPath: (schema: string) => string;
|
|
18
|
+
export declare const shapeEntityPath: (schema: string, entity: string) => string;
|
package/dist/app/paths.js
CHANGED
|
@@ -1,32 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
export
|
|
8
|
-
return `${hornsPath(datastoreId)}/${encodeURIComponent(slug)}`;
|
|
9
|
-
}
|
|
1
|
+
// Relative to the datastore: `ActiveDatastoreProvider` scopes the router's basename to `/d/<datastoreId>`,
|
|
2
|
+
// so a helper that wrote the prefix itself would double it.
|
|
3
|
+
export const hornsPath = () => "/horns";
|
|
4
|
+
export const newHornPath = () => "/horns/new";
|
|
5
|
+
/** A new Horn started from the thing it is about: a rule, an action plan. The IRI rides as a parameter. */
|
|
6
|
+
export const newHornAboutPath = (iri) => `/horns/new?about=${encodeURIComponent(iri)}`;
|
|
7
|
+
export const hornPath = (slug) => `/horns/${encodeURIComponent(slug)}`;
|
|
10
8
|
/** A saved Horn as the thing itself: what a shared link opens, with the variables it carries. */
|
|
11
|
-
export
|
|
12
|
-
return `${hornPath(datastoreId, slug)}/view`;
|
|
13
|
-
}
|
|
14
|
-
export function shapesPath(datastoreId) {
|
|
15
|
-
return `/d/${encodeURIComponent(datastoreId)}/shapes`;
|
|
16
|
-
}
|
|
17
|
-
export function shapeZooPath(datastoreId, schema) {
|
|
18
|
-
return `${shapesPath(datastoreId)}/${encodeURIComponent(schema)}`;
|
|
19
|
-
}
|
|
20
|
-
// An entity IRI carries slashes and colons, so the entity travels as a query parameter rather than a segment.
|
|
21
|
-
export function shapeEntityPath(datastoreId, schema, entity) {
|
|
22
|
-
return `${shapeZooPath(datastoreId, schema)}/entity?iri=${encodeURIComponent(entity)}`;
|
|
23
|
-
}
|
|
9
|
+
export const hornViewPath = (slug) => `${hornPath(slug)}/view`;
|
|
24
10
|
/** A datastore's Horns as cards: what each one answers, who it is for, what it reads. */
|
|
25
|
-
export const showcasePath = (
|
|
11
|
+
export const showcasePath = () => "/horns/showcase";
|
|
26
12
|
/** What Manta works out about a datastore: every rule, and whether it can be looked at. */
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
13
|
+
export const rulesPath = () => "/horns/rules";
|
|
14
|
+
export const plansPath = () => "/horns/plans";
|
|
15
|
+
export const rulePath = (ruleId) => `${rulesPath()}/${encodeURIComponent(ruleId)}`;
|
|
16
|
+
/** The reference Horn drawn against a fixture. Dev only; see docs/standard.md. */
|
|
17
|
+
export const referencePath = () => "/horns/reference";
|
|
18
|
+
export const shapesPath = () => "/horns/shapes";
|
|
19
|
+
export const shapeZooPath = (schema) => `${shapesPath()}/${encodeURIComponent(schema)}`;
|
|
20
|
+
// An entity IRI carries slashes and colons, so the entity travels as a query parameter rather than a segment.
|
|
21
|
+
export const shapeEntityPath = (schema, entity) => `${shapeZooPath(schema)}/entity?iri=${encodeURIComponent(entity)}`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function PlansPage(): import("react").JSX.Element;
|