@manta-eu/react 0.1.10 → 0.2.0
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/dist/components/chat-view/ChatTurnErrorNotice.d.ts.map +1 -1
- package/dist/components/chat-view/ChatTurnErrorNotice.js +3 -1
- package/dist/components/chat-view/ChatTurnErrorNotice.js.map +1 -1
- package/dist/components/layouts/ConnectedCustomApp.d.ts +13 -0
- package/dist/components/layouts/ConnectedCustomApp.d.ts.map +1 -0
- package/dist/components/layouts/ConnectedCustomApp.js +37 -0
- package/dist/components/layouts/ConnectedCustomApp.js.map +1 -0
- package/dist/components/layouts/Sidebar.d.ts.map +1 -1
- package/dist/components/layouts/Sidebar.js +6 -3
- package/dist/components/layouts/Sidebar.js.map +1 -1
- package/dist/components/table-skeleton/index.d.ts +2 -0
- package/dist/components/table-skeleton/index.d.ts.map +1 -1
- package/dist/components/table-skeleton/index.js +2 -1
- package/dist/components/table-skeleton/index.js.map +1 -1
- package/dist/graph/createNodeRouter.js +3 -3
- package/dist/graph/createNodeRouter.js.map +1 -1
- package/dist/graph/nodes/BrandFontSuggestionView.d.ts +3 -0
- package/dist/graph/nodes/BrandFontSuggestionView.d.ts.map +1 -0
- package/dist/graph/nodes/{BrandFontFamilyView.js → BrandFontSuggestionView.js} +21 -13
- package/dist/graph/nodes/BrandFontSuggestionView.js.map +1 -0
- package/dist/graph/nodes/brand-font.d.ts +8 -0
- package/dist/graph/nodes/brand-font.d.ts.map +1 -1
- package/dist/graph/nodes/brand-font.js +6 -0
- package/dist/graph/nodes/brand-font.js.map +1 -1
- package/dist/graph/nodes/brand-shared.d.ts +0 -3
- package/dist/graph/nodes/brand-shared.d.ts.map +1 -1
- package/dist/graph/nodes/brand-shared.js +1 -4
- package/dist/graph/nodes/brand-shared.js.map +1 -1
- package/dist/hooks/use-connection-problems.d.ts.map +1 -1
- package/dist/hooks/useAuth.d.ts +2 -2
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +2 -2
- package/dist/hooks/useAuth.js.map +1 -1
- package/dist/hooks/useOnboardingState.js +1 -1
- package/dist/hooks/useOnboardingState.js.map +1 -1
- package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
- package/dist/lib/datastore-route-table.d.ts +2 -1
- package/dist/lib/datastore-route-table.d.ts.map +1 -1
- package/dist/lib/datastore-route-table.js +2 -1
- package/dist/lib/datastore-route-table.js.map +1 -1
- package/dist/providers/AuthContext.d.ts +2 -1
- package/dist/providers/AuthContext.d.ts.map +1 -1
- package/dist/providers/AuthContext.js.map +1 -1
- package/dist/providers/ChatManagerProvider.d.ts.map +1 -1
- package/dist/providers/ChatManagerProvider.js +19 -8
- package/dist/providers/ChatManagerProvider.js.map +1 -1
- package/dist/routes/data-explorer/declared-namespaces.d.ts +3 -0
- package/dist/routes/data-explorer/declared-namespaces.d.ts.map +1 -1
- package/dist/routes/data-explorer/declared-namespaces.js +21 -13
- package/dist/routes/data-explorer/declared-namespaces.js.map +1 -1
- package/dist/routes/data-explorer/namespaces.d.ts +1 -0
- package/dist/routes/data-explorer/namespaces.d.ts.map +1 -1
- package/dist/routes/data-explorer/namespaces.js +7 -6
- package/dist/routes/data-explorer/namespaces.js.map +1 -1
- package/dist/routes/datastores/components/DeleteDatastoreDialog.d.ts +10 -0
- package/dist/routes/datastores/components/DeleteDatastoreDialog.d.ts.map +1 -0
- package/dist/routes/datastores/components/DeleteDatastoreDialog.js +20 -0
- package/dist/routes/datastores/components/DeleteDatastoreDialog.js.map +1 -0
- package/dist/routes/datastores/flatten.d.ts +1 -0
- package/dist/routes/datastores/flatten.d.ts.map +1 -1
- package/dist/routes/datastores/flatten.js +17 -0
- package/dist/routes/datastores/flatten.js.map +1 -1
- package/dist/routes/datastores/hooks/useDatastoreColumns.d.ts +3 -1
- package/dist/routes/datastores/hooks/useDatastoreColumns.d.ts.map +1 -1
- package/dist/routes/datastores/hooks/useDatastoreColumns.js +14 -6
- package/dist/routes/datastores/hooks/useDatastoreColumns.js.map +1 -1
- package/dist/routes/datastores/index.d.ts.map +1 -1
- package/dist/routes/datastores/index.js +31 -5
- package/dist/routes/datastores/index.js.map +1 -1
- package/dist/routes/integrations/components/ProviderGroup.js +2 -2
- package/dist/routes/integrations/components/ProviderGroup.js.map +1 -1
- package/package.json +7 -7
- package/src/components/chat-view/ChatTurnErrorNotice.tsx +3 -1
- package/src/components/layouts/ConnectedCustomApp.tsx +63 -0
- package/src/components/layouts/Sidebar.tsx +9 -3
- package/src/components/table-skeleton/index.tsx +7 -1
- package/src/graph/createNodeRouter.tsx +3 -3
- package/src/graph/nodes/{BrandFontFamilyView.tsx → BrandFontSuggestionView.tsx} +39 -31
- package/src/graph/nodes/brand-font.ts +17 -0
- package/src/graph/nodes/brand-shared.tsx +1 -4
- package/src/hooks/useAuth.ts +3 -3
- package/src/hooks/useOnboardingState.ts +1 -1
- package/src/lib/datastore-route-table.ts +4 -2
- package/src/providers/AuthContext.ts +3 -1
- package/src/providers/ChatManagerProvider.tsx +16 -8
- package/src/routes/data-explorer/declared-namespaces.ts +21 -13
- package/src/routes/data-explorer/namespaces.ts +7 -6
- package/src/routes/datastores/components/DeleteDatastoreDialog.tsx +78 -0
- package/src/routes/datastores/flatten.ts +15 -0
- package/src/routes/datastores/hooks/useDatastoreColumns.tsx +88 -49
- package/src/routes/datastores/index.tsx +41 -2
- package/src/routes/integrations/components/ProviderGroup.tsx +2 -2
- package/dist/graph/nodes/BrandFontFamilyView.d.ts +0 -3
- package/dist/graph/nodes/BrandFontFamilyView.d.ts.map +0 -1
- package/dist/graph/nodes/BrandFontFamilyView.js.map +0 -1
- package/dist/graph/nodes/BrandFontUploadButton.d.ts +0 -6
- package/dist/graph/nodes/BrandFontUploadButton.d.ts.map +0 -1
- package/dist/graph/nodes/BrandFontUploadButton.js +0 -69
- package/dist/graph/nodes/BrandFontUploadButton.js.map +0 -1
- package/src/graph/nodes/BrandFontUploadButton.tsx +0 -95
|
@@ -9,14 +9,19 @@ export type TableSkeletonProps = {
|
|
|
9
9
|
renderRow: (rowIndex: number) => ReactNode;
|
|
10
10
|
withWrapper?: boolean;
|
|
11
11
|
className?: string;
|
|
12
|
+
headerClassName?: string;
|
|
13
|
+
rowClassName?: string;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
export function TableSkeleton(props: TableSkeletonProps) {
|
|
15
17
|
const { gridColsClassName, headerColumnCount, rows = 8, renderRow, withWrapper = true, className } = props;
|
|
18
|
+
const { headerClassName, rowClassName } = props;
|
|
16
19
|
|
|
17
20
|
const content = (
|
|
18
21
|
<>
|
|
19
|
-
<div
|
|
22
|
+
<div
|
|
23
|
+
className={cn("grid gap-3 border-b border-muted-border bg-faint px-4 py-3", gridColsClassName, headerClassName)}
|
|
24
|
+
>
|
|
20
25
|
{Array.from({ length: headerColumnCount }, (_, index) => (
|
|
21
26
|
<Skeleton key={`table-skeleton-header-${index}`} className="h-4 w-2/3 rounded-md" />
|
|
22
27
|
))}
|
|
@@ -27,6 +32,7 @@ export function TableSkeleton(props: TableSkeletonProps) {
|
|
|
27
32
|
className={cn(
|
|
28
33
|
"grid items-center gap-3 border-b border-muted-border px-4 py-4 last:border-b-0",
|
|
29
34
|
gridColsClassName,
|
|
35
|
+
rowClassName,
|
|
30
36
|
)}
|
|
31
37
|
>
|
|
32
38
|
{renderRow(rowIndex)}
|
|
@@ -12,7 +12,7 @@ import { AttioNoteView } from "./nodes/AttioNoteView";
|
|
|
12
12
|
import { AttioTaskView } from "./nodes/AttioTaskView";
|
|
13
13
|
import { BrandAssetAnalysisActionView } from "./nodes/BrandAssetAnalysisActionView";
|
|
14
14
|
import { BrandAssetAnalysisView } from "./nodes/BrandAssetAnalysisView";
|
|
15
|
-
import {
|
|
15
|
+
import { BrandFontSuggestionView } from "./nodes/BrandFontSuggestionView";
|
|
16
16
|
import { BrandGuidelinesView } from "./nodes/BrandGuidelinesView";
|
|
17
17
|
import { CoresignalCompanyView } from "./nodes/CoresignalCompanyView";
|
|
18
18
|
import { CoresignalEmployeeView } from "./nodes/CoresignalEmployeeView";
|
|
@@ -123,7 +123,7 @@ const typedNodeEntries: ReadonlyArray<readonly [IRI, EntityView]> = [
|
|
|
123
123
|
// Asserted by a Datalog rule, not by a node shape, so codegen emits no class term for it.
|
|
124
124
|
[expandPrefix("brand:AssetAnalysis"), BrandAssetAnalysisView],
|
|
125
125
|
[brand.BrandGuidelines, BrandGuidelinesView],
|
|
126
|
-
[brand.
|
|
126
|
+
[brand.FontSuggestion, BrandFontSuggestionView],
|
|
127
127
|
];
|
|
128
128
|
|
|
129
129
|
export const typedEntityTypeIris: ReadonlySet<IRI> = new Set(typedNodeEntries.map(([type]) => type as IRI));
|
|
@@ -135,7 +135,7 @@ export const diffSubsumingViewIris: ReadonlySet<IRI> = new Set([
|
|
|
135
135
|
expandPrefix("brand:AssetAnalysisShape"),
|
|
136
136
|
expandPrefix("brand:AssetAnalysis"),
|
|
137
137
|
brand.BrandGuidelines,
|
|
138
|
-
brand.
|
|
138
|
+
brand.FontSuggestion,
|
|
139
139
|
]);
|
|
140
140
|
|
|
141
141
|
export const typedActionShapeIris: ReadonlySet<IRI> = new Set(actionShapeNodeEntries.map(([iri]) => iri));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BrandFontFileSchema } from "@manta-eu/ldo";
|
|
1
|
+
import { BrandFontFileSchema, BrandSuggestedWeightSchema } from "@manta-eu/ldo";
|
|
2
2
|
import { redirectPath } from "@manta-eu/ldo/redirect-queries";
|
|
3
3
|
import { isPresent } from "@manta-eu/lib/present";
|
|
4
4
|
import { namespaces } from "@manta-eu/rdf-lib/namespaces";
|
|
@@ -8,11 +8,13 @@ import type { EntityViewProps } from "../types";
|
|
|
8
8
|
import {
|
|
9
9
|
fontFaceDescriptors,
|
|
10
10
|
preferredFontFile,
|
|
11
|
+
sortedWeights,
|
|
11
12
|
specimenWeight,
|
|
13
|
+
weightLabel,
|
|
12
14
|
type StoredFontFile,
|
|
15
|
+
type SuggestedWeight,
|
|
13
16
|
} from "./brand-font";
|
|
14
17
|
import { BrandNodeBody, BrandStatusBadge } from "./brand-shared";
|
|
15
|
-
import { BrandFontUploadButton } from "./BrandFontUploadButton";
|
|
16
18
|
import { EntityCardShell } from "./EntityCardShell";
|
|
17
19
|
import { NodePropertyTable } from "./NodePropertyTable";
|
|
18
20
|
import { readBrandString, toIriArray } from "./typed-node-helpers";
|
|
@@ -20,11 +22,26 @@ import { useEntitiesByIris } from "#hooks/use-entity";
|
|
|
20
22
|
import { useFontFace } from "#hooks/use-font-face";
|
|
21
23
|
import { useRedirectEndpoint } from "#hooks/use-redirect-endpoint";
|
|
22
24
|
|
|
23
|
-
const
|
|
24
|
-
const availabilityPredicate = `${namespaces.brand}fontAvailability`;
|
|
25
|
-
const fontFilePredicate = `${namespaces.brand}fontFile`;
|
|
25
|
+
const weightPredicate = `${namespaces.brand}hasSuggestedWeight`;
|
|
26
26
|
const specimenText = "Aa Bb Cc Qq Rr 0123456789 — The quick brown fox jumps over the lazy dog";
|
|
27
27
|
|
|
28
|
+
function useSuggestedWeights(iris: string[]): SuggestedWeight[] {
|
|
29
|
+
const { data } = useEntitiesByIris({ schema: BrandSuggestedWeightSchema, iris });
|
|
30
|
+
|
|
31
|
+
return (data ?? []).flatMap((weight) =>
|
|
32
|
+
weight
|
|
33
|
+
? [
|
|
34
|
+
{
|
|
35
|
+
weight: weight.fontWeight,
|
|
36
|
+
style: weight.fontStyle,
|
|
37
|
+
availability: weight.fontAvailability,
|
|
38
|
+
coveringFiles: weight.coveringFile ?? [],
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
: [],
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
function useStoredFontFiles(iris: string[]): StoredFontFile[] {
|
|
29
46
|
const { data } = useEntitiesByIris({ schema: BrandFontFileSchema, iris });
|
|
30
47
|
|
|
@@ -69,48 +86,39 @@ function FontSpecimen(props: FontSpecimenProps) {
|
|
|
69
86
|
);
|
|
70
87
|
}
|
|
71
88
|
|
|
72
|
-
export const
|
|
89
|
+
export const BrandFontSuggestionView = memo(function BrandFontSuggestionView(props: EntityViewProps) {
|
|
73
90
|
const { data, selected = false, ringColor, onContextMenu } = props;
|
|
74
91
|
|
|
75
92
|
const family = readBrandString(data.jsonLd, "family") ?? data.label;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const file = preferredFontFile(useStoredFontFiles(
|
|
93
|
+
const weights = sortedWeights(
|
|
94
|
+
useSuggestedWeights(toIriArray(data.jsonLd[weightPredicate] ?? data.jsonLd["brand:hasSuggestedWeight"])),
|
|
95
|
+
);
|
|
96
|
+
const file = preferredFontFile(useStoredFontFiles([...new Set(weights.flatMap((weight) => weight.coveringFiles))]));
|
|
80
97
|
|
|
81
98
|
return (
|
|
82
99
|
<EntityCardShell
|
|
83
100
|
icon={Type}
|
|
84
|
-
label={`Font
|
|
101
|
+
label={`Font Suggestion: ${family}`}
|
|
85
102
|
labelMaxWidth="max-w-140"
|
|
86
103
|
selected={selected}
|
|
87
104
|
ringColor={ringColor}
|
|
88
105
|
onContextMenu={onContextMenu}
|
|
89
106
|
>
|
|
90
|
-
<BrandNodeBody
|
|
91
|
-
right={
|
|
92
|
-
<div className="flex items-center gap-1">
|
|
93
|
-
<BrandStatusBadge status={availability} />
|
|
94
|
-
<BrandStatusBadge status={status} />
|
|
95
|
-
</div>
|
|
96
|
-
}
|
|
97
|
-
>
|
|
107
|
+
<BrandNodeBody>
|
|
98
108
|
{isPresent(file) && <FontSpecimen file={file} />}
|
|
99
109
|
|
|
100
|
-
{isPresent(
|
|
101
|
-
<
|
|
102
|
-
{
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
{isPresent(weights) && (
|
|
111
|
+
<ul className="flex flex-col gap-1">
|
|
112
|
+
{weights.map((weight) => (
|
|
113
|
+
<li key={weightLabel(weight)} className="flex items-center justify-between gap-2">
|
|
114
|
+
<span className="typography-tiny text-default-foreground">{weightLabel(weight)}</span>
|
|
115
|
+
<BrandStatusBadge status={weight.availability} />
|
|
116
|
+
</li>
|
|
117
|
+
))}
|
|
118
|
+
</ul>
|
|
107
119
|
)}
|
|
108
120
|
|
|
109
|
-
<NodePropertyTable
|
|
110
|
-
properties={data.properties}
|
|
111
|
-
jsonLd={data.jsonLd}
|
|
112
|
-
omitPredicates={[statusPredicate, availabilityPredicate]}
|
|
113
|
-
/>
|
|
121
|
+
<NodePropertyTable properties={data.properties} jsonLd={data.jsonLd} omitPredicates={[weightPredicate]} />
|
|
114
122
|
</BrandNodeBody>
|
|
115
123
|
</EntityCardShell>
|
|
116
124
|
);
|
|
@@ -27,3 +27,20 @@ export function fontFaceDescriptors(file: StoredFontFile): FontFaceDescriptors {
|
|
|
27
27
|
export function specimenWeight(file: StoredFontFile): number {
|
|
28
28
|
return Math.min(Math.max(400, file.weightMin), file.weightMax);
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
export type SuggestedWeight = {
|
|
32
|
+
weight: number;
|
|
33
|
+
style: "normal" | "italic";
|
|
34
|
+
availability: "available" | "looking-up" | "needs-upload";
|
|
35
|
+
coveringFiles: readonly string[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function sortedWeights<W extends Pick<SuggestedWeight, "weight" | "style">>(weights: readonly W[]): W[] {
|
|
39
|
+
return [...weights].sort(
|
|
40
|
+
(a, b) => a.weight - b.weight || Number(a.style === "italic") - Number(b.style === "italic"),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function weightLabel(weight: Pick<SuggestedWeight, "weight" | "style">): string {
|
|
45
|
+
return weight.style === "italic" ? `${weight.weight} italic` : `${weight.weight}`;
|
|
46
|
+
}
|
|
@@ -11,14 +11,11 @@ import { cn } from "#style";
|
|
|
11
11
|
|
|
12
12
|
export const brandAmber = "#d97706";
|
|
13
13
|
|
|
14
|
-
// Every `sh:in` value
|
|
14
|
+
// Every `sh:in` value of brand:fontAvailability in kgc/shacl/shapes/brand-font-suggestion.ttl.
|
|
15
15
|
const toneByStatus = {
|
|
16
|
-
accepted: "positive",
|
|
17
16
|
available: "positive",
|
|
18
|
-
draft: "neutral",
|
|
19
17
|
"looking-up": "neutral",
|
|
20
18
|
"needs-upload": "caution",
|
|
21
|
-
rejected: "negative",
|
|
22
19
|
} as const satisfies Record<string, BadgeTone>;
|
|
23
20
|
|
|
24
21
|
export type BrandStatus = keyof typeof toneByStatus;
|
package/src/hooks/useAuth.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type UseAuthResult = {
|
|
|
26
26
|
isAuthenticated: boolean;
|
|
27
27
|
isOperator: boolean;
|
|
28
28
|
canManageTeam: boolean;
|
|
29
|
-
|
|
29
|
+
canManageCustomApps: boolean;
|
|
30
30
|
orgRoles: OrgRoleDefinition[];
|
|
31
31
|
canManageIntegrations: boolean;
|
|
32
32
|
canManageStreams: boolean;
|
|
@@ -79,7 +79,7 @@ export const contextResponseSchema = z.object({
|
|
|
79
79
|
canManageStreams: z.boolean().optional().default(false),
|
|
80
80
|
canManageSharedMemories: z.boolean().optional().default(false),
|
|
81
81
|
canManageTeam: z.boolean().optional().default(false),
|
|
82
|
-
|
|
82
|
+
canManageCustomApps: z.boolean().optional().default(false),
|
|
83
83
|
datastore: datastoreSchema.nullish(),
|
|
84
84
|
}),
|
|
85
85
|
datastores: z.array(datastoreSchema),
|
|
@@ -297,7 +297,7 @@ export function useAuth(): UseAuthResult {
|
|
|
297
297
|
datastoreAccessGranted: contextData?.datastoreAccessGranted ?? true,
|
|
298
298
|
isOperator: contextData?.user?.isOperator ?? false,
|
|
299
299
|
canManageTeam: contextData?.user?.canManageTeam ?? false,
|
|
300
|
-
|
|
300
|
+
canManageCustomApps: contextData?.user?.canManageCustomApps ?? false,
|
|
301
301
|
orgRoles: contextData?.orgRoles ?? [],
|
|
302
302
|
canManageIntegrations: contextData?.user?.canManageIntegrations ?? false,
|
|
303
303
|
canManageStreams: contextData?.user?.canManageStreams ?? false,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { onboardingCompletedCookieName as onboardingCookieName } from "@manta-eu/client/shared/constants";
|
|
1
2
|
import Cookies from "js-cookie";
|
|
2
3
|
import { useCallback, useState } from "react";
|
|
3
4
|
|
|
4
|
-
const onboardingCookieName = "manta-onboarding-completed";
|
|
5
5
|
const onboardingCookieExpiryDays = 365;
|
|
6
6
|
|
|
7
7
|
type OnboardingState = {
|
|
@@ -17,7 +17,8 @@ export type RouteElements = {
|
|
|
17
17
|
Integrations?: ReactNode;
|
|
18
18
|
ShopifyConnect?: ReactNode;
|
|
19
19
|
Team?: ReactNode;
|
|
20
|
-
|
|
20
|
+
CustomApps?: ReactNode;
|
|
21
|
+
CustomAppUsers?: ReactNode;
|
|
21
22
|
DataExplorerLayout?: ReactNode;
|
|
22
23
|
DataExplorerIndex?: ReactNode;
|
|
23
24
|
DataExplorerRecent?: ReactNode;
|
|
@@ -45,7 +46,8 @@ export const globalRoutes: RouteDefinition[] = [
|
|
|
45
46
|
{ path: "/datastores", render: (e) => e.Datastores || notFoundElement(e) },
|
|
46
47
|
// Global, not datastore-scoped: Shopify sends the merchant here before any datastore is known.
|
|
47
48
|
{ path: "/shopify/connect", render: (e) => e.ShopifyConnect || notFoundElement(e) },
|
|
48
|
-
{ path: "/custom-apps", render: (e) => e.
|
|
49
|
+
{ path: "/custom-apps", render: (e) => e.CustomApps || notFoundElement(e) },
|
|
50
|
+
{ path: "/custom-apps/:clientId/users", render: (e) => e.CustomAppUsers || notFoundElement(e) },
|
|
49
51
|
];
|
|
50
52
|
|
|
51
53
|
export const datastoreRoutes: RouteDefinition[] = [
|
|
@@ -9,7 +9,7 @@ export type AuthContextValue = Pick<
|
|
|
9
9
|
| "logout"
|
|
10
10
|
| "isOperator"
|
|
11
11
|
| "canManageTeam"
|
|
12
|
-
| "
|
|
12
|
+
| "canManageCustomApps"
|
|
13
13
|
| "orgRoles"
|
|
14
14
|
| "canManageIntegrations"
|
|
15
15
|
| "canManageStreams"
|
|
@@ -18,6 +18,8 @@ export type AuthContextValue = Pick<
|
|
|
18
18
|
| "allowedModels"
|
|
19
19
|
> & {
|
|
20
20
|
user: User;
|
|
21
|
+
// Only Manta's own web app may list the apps connected to someone; any other host's token is refused.
|
|
22
|
+
canDisconnectCustomApps?: boolean;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export const AuthContext = createContext<AuthContextValue | undefined>(undefined);
|
|
@@ -80,6 +80,7 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
80
80
|
const conversationSelectionRef = useRef(conversationSelection);
|
|
81
81
|
const pathnameRef = useRef(location.pathname);
|
|
82
82
|
const routedConversationIdRef = useRef(routedConversationId);
|
|
83
|
+
const pendingPlanNavigationRef = useRef<{ pathname: string } | undefined>(undefined);
|
|
83
84
|
|
|
84
85
|
// Where navigate() was last sent, which react-router's deferred location has not reached yet.
|
|
85
86
|
const requestedPathRef = useRef(location.pathname);
|
|
@@ -351,7 +352,11 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
351
352
|
);
|
|
352
353
|
|
|
353
354
|
const selectContext = useCallback(
|
|
354
|
-
(context: ChatContext): Promise<void> =>
|
|
355
|
+
(context: ChatContext): Promise<void> => {
|
|
356
|
+
// The current page may synchronize its old context while an explicit plan selection is waiting.
|
|
357
|
+
if (pendingPlanNavigationRef.current?.pathname === pathnameRef.current) return Promise.resolve();
|
|
358
|
+
return transitionContext({ kind: "select", context }).then(() => undefined);
|
|
359
|
+
},
|
|
355
360
|
[transitionContext],
|
|
356
361
|
);
|
|
357
362
|
|
|
@@ -390,17 +395,20 @@ export function ChatManagerProvider(props: ChatManagerProviderProps) {
|
|
|
390
395
|
|
|
391
396
|
const context = { kind: ChatContextKind.Plan, planGraphIri: planIri } as const;
|
|
392
397
|
const source = activeConversationFromSelection(conversationSelectionRef.current);
|
|
393
|
-
|
|
394
|
-
|
|
398
|
+
const navigation = { pathname: pathnameRef.current };
|
|
399
|
+
pendingPlanNavigationRef.current = navigation;
|
|
400
|
+
try {
|
|
401
|
+
if (source) {
|
|
395
402
|
const transitioned = await reassignCurrentConversation(context);
|
|
396
403
|
if (!transitioned) return;
|
|
397
|
-
} catch {
|
|
398
|
-
toast.error("Couldn't move this chat to the plan. Please try again.");
|
|
399
|
-
return;
|
|
400
404
|
}
|
|
401
|
-
}
|
|
402
405
|
|
|
403
|
-
|
|
406
|
+
void navigate(buildPlanHref(planIri), { state: { fromChat: true } });
|
|
407
|
+
} catch {
|
|
408
|
+
toast.error("Couldn't move this chat to the plan. Please try again.");
|
|
409
|
+
} finally {
|
|
410
|
+
if (pendingPlanNavigationRef.current === navigation) pendingPlanNavigationRef.current = undefined;
|
|
411
|
+
}
|
|
404
412
|
},
|
|
405
413
|
[navigate, onSelectPlan, reassignCurrentConversation],
|
|
406
414
|
);
|
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
import { ontologyGraphIri } from "@manta-eu/client/shared/rdf-namespaces";
|
|
1
|
+
import { expandPrefix, ontologyGraphIri } from "@manta-eu/client/shared/rdf-namespaces";
|
|
2
2
|
import { isPresent } from "@manta-eu/lib/present";
|
|
3
3
|
import { DataFactory, type RDF } from "ldkit/rdf";
|
|
4
4
|
import { SELECT } from "ldkit/sparql";
|
|
5
5
|
import { useMemo } from "react";
|
|
6
|
-
import { prefixes, registerDeclaredNamespaces, type DeclaredNamespace } from "./namespaces";
|
|
6
|
+
import { prefixes, realmPath, registerDeclaredNamespaces, type DeclaredNamespace } from "./namespaces";
|
|
7
7
|
import { bindingValue, useSparqlSelect } from "#ldo-ui";
|
|
8
8
|
|
|
9
9
|
const df = new DataFactory();
|
|
10
10
|
|
|
11
|
-
const declaredNamespacesQuery = `${prefixes}${SELECT`?prefix ?namespace ?source ?title ?sourceLabel
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
?declaration
|
|
17
|
-
OPTIONAL {
|
|
18
|
-
|
|
11
|
+
const declaredNamespacesQuery = `${prefixes}${SELECT`?prefix ?namespace ?source ?title ?sourceLabel ?connectionName`
|
|
12
|
+
.WHERE`
|
|
13
|
+
{
|
|
14
|
+
GRAPH ${df.namedNode(ontologyGraphIri)} {
|
|
15
|
+
[] sh:declare ?declaration .
|
|
16
|
+
?declaration sh:prefix ?prefix ; sh:namespace ?namespace .
|
|
17
|
+
OPTIONAL {
|
|
18
|
+
?declaration rdfs:isDefinedBy ?source .
|
|
19
|
+
OPTIONAL { ?source dcterms:title ?title }
|
|
20
|
+
OPTIONAL { ?source rdfs:label ?sourceLabel }
|
|
21
|
+
}
|
|
19
22
|
}
|
|
23
|
+
} UNION {
|
|
24
|
+
?declaration a sh:PrefixDeclaration ; sh:namespace ?namespace ; schema:identifier ?id .
|
|
25
|
+
?connection a ${df.namedNode(expandPrefix(":DataConnection"))} ; schema:identifier ?id ; schema:name ?connectionName .
|
|
26
|
+
FILTER NOT EXISTS { ?connection ${df.namedNode(expandPrefix(":deletedAt"))} ?deleted }
|
|
20
27
|
}
|
|
21
28
|
`.build()}`;
|
|
22
29
|
|
|
@@ -28,15 +35,16 @@ function sourceFrom(binding: RDF.Bindings) {
|
|
|
28
35
|
return { iri, label: bindingValue(binding, "title") ?? bindingValue(binding, "sourceLabel") ?? iri };
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
function declaredNamespacesFrom(bindings: RDF.Bindings[]): Record<string, DeclaredNamespace> {
|
|
38
|
+
export function declaredNamespacesFrom(bindings: RDF.Bindings[]): Record<string, DeclaredNamespace> {
|
|
32
39
|
return Object.fromEntries(
|
|
33
40
|
bindings.flatMap<[string, DeclaredNamespace]>((binding) => {
|
|
34
|
-
const prefix = bindingValue(binding, "prefix");
|
|
35
41
|
const iri = bindingValue(binding, "namespace");
|
|
42
|
+
const connectionName = bindingValue(binding, "connectionName");
|
|
43
|
+
const prefix = isPresent(connectionName) && isPresent(iri) ? realmPath(iri) : bindingValue(binding, "prefix");
|
|
36
44
|
|
|
37
45
|
if (!isPresent(prefix) || !isPresent(iri)) return [];
|
|
38
46
|
|
|
39
|
-
return [[prefix, { iri, label: prefix, source: sourceFrom(binding) }]];
|
|
47
|
+
return [[prefix, { iri, label: connectionName ?? prefix, source: sourceFrom(binding) }]];
|
|
40
48
|
}),
|
|
41
49
|
);
|
|
42
50
|
}
|
|
@@ -78,12 +78,13 @@ export function prefixForIri(iri: string): string | undefined {
|
|
|
78
78
|
|
|
79
79
|
if (registered) return registered;
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
return realmPath(iri);
|
|
82
|
+
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
export function realmPath(iri: string): string | undefined {
|
|
85
|
+
if (!iri.startsWith(realmVocabRoot) || !iri.includes("#")) return undefined;
|
|
85
86
|
|
|
86
|
-
return
|
|
87
|
+
return iri.slice(realmVocabRoot.length).split("#")[0] || undefined;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
export function localName(iri: string, prefix: string): string {
|
|
@@ -118,11 +119,11 @@ export function groupCrumb(prefix: string): { label: string; to: string } {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
export function classPath(prefix: string, name: string): string {
|
|
121
|
-
return `${
|
|
122
|
+
return `${namespacePath(prefix)}/${name}`;
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
export function recordPath(prefix: string, type: string, id: string): string {
|
|
125
|
-
return `${
|
|
126
|
+
return `${classPath(prefix, type)}/${encodeURIComponent(id)}`;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
export type ClassRef = { iri: string; prefix: string; localName: string; path: string };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { DatastoreRow } from "../flatten";
|
|
3
|
+
import { Button } from "#components/button/index";
|
|
4
|
+
import { Input } from "#components/input/index";
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
DialogFooter,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
} from "#components/modal/index";
|
|
13
|
+
|
|
14
|
+
type DeleteDatastoreDialogProps = {
|
|
15
|
+
target: DatastoreRow;
|
|
16
|
+
isDeleting: boolean;
|
|
17
|
+
onConfirm: (confirmName: string) => void;
|
|
18
|
+
onCancel: () => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function DeleteDatastoreDialog(props: DeleteDatastoreDialogProps) {
|
|
22
|
+
const { target, isDeleting, onConfirm, onCancel } = props;
|
|
23
|
+
const [typed, setTyped] = useState("");
|
|
24
|
+
|
|
25
|
+
const canDelete = typed.trim() === target.name.trim() && !isDeleting;
|
|
26
|
+
const deletesOrganization = target.isOrganization && target.mirrored !== true;
|
|
27
|
+
|
|
28
|
+
function confirm() {
|
|
29
|
+
if (canDelete) onConfirm(target.name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Dialog open onOpenChange={(nextOpen) => !nextOpen && !isDeleting && onCancel()}>
|
|
34
|
+
<DialogContent className="flex flex-col overflow-hidden" showCloseButton={!isDeleting}>
|
|
35
|
+
<DialogHeader>
|
|
36
|
+
<DialogTitle
|
|
37
|
+
text={deletesOrganization ? "Delete organization permanently" : "Delete datastore permanently"}
|
|
38
|
+
/>
|
|
39
|
+
<DialogDescription>This can't be undone. Deleting "{target.name}" removes:</DialogDescription>
|
|
40
|
+
</DialogHeader>
|
|
41
|
+
|
|
42
|
+
<ul className="list-disc pl-5 typography-small text-muted-foreground">
|
|
43
|
+
<li>its knowledge graph and everything in it</li>
|
|
44
|
+
<li>its connected integrations and their secrets</li>
|
|
45
|
+
<li>its uploaded files and chats</li>
|
|
46
|
+
{deletesOrganization && <li>the organization, its groups and memberships</li>}
|
|
47
|
+
{!target.isOrganization && !target.isUnlinked && <li>the group and its memberships</li>}
|
|
48
|
+
</ul>
|
|
49
|
+
{target.isOrganization && !deletesOrganization && (
|
|
50
|
+
<p className="typography-small text-muted-foreground">
|
|
51
|
+
The organization itself stays, because the identity provider holds it. It is listed as having no datastore
|
|
52
|
+
afterwards.
|
|
53
|
+
</p>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
<Input
|
|
57
|
+
autoFocus
|
|
58
|
+
label={`Type "${target.name}" to confirm`}
|
|
59
|
+
value={typed}
|
|
60
|
+
disabled={isDeleting}
|
|
61
|
+
onChange={(event) => setTyped(event.target.value)}
|
|
62
|
+
onKeyDown={(event) => {
|
|
63
|
+
if (event.key === "Enter") confirm();
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
<DialogFooter>
|
|
68
|
+
<Button variant="tertiary" onClick={onCancel} disabled={isDeleting}>
|
|
69
|
+
Cancel
|
|
70
|
+
</Button>
|
|
71
|
+
<Button variant="destructive" onClick={confirm} disabled={!canDelete} isLoading={isDeleting}>
|
|
72
|
+
Delete permanently
|
|
73
|
+
</Button>
|
|
74
|
+
</DialogFooter>
|
|
75
|
+
</DialogContent>
|
|
76
|
+
</Dialog>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -79,6 +79,21 @@ export function visibleDatastoreRows(rows: DatastoreRow[], visibility: Datastore
|
|
|
79
79
|
return rows.filter((_row, index) => shown[index] || hasVisibleDescendant(rows, shown, index));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// The server refuses to delete a datastore while others live under it.
|
|
83
|
+
export function nestedDatastoreNamesByRowId(rows: DatastoreRow[]): Map<string, string[]> {
|
|
84
|
+
const nested = new Map<string, string[]>();
|
|
85
|
+
rows.forEach((row, index) => {
|
|
86
|
+
const names: string[] = [];
|
|
87
|
+
for (let i = index + 1; i < rows.length; i++) {
|
|
88
|
+
const descendant = rows[i];
|
|
89
|
+
if (!descendant || descendant.depth <= row.depth) break;
|
|
90
|
+
if (isPresent(descendant.datastoreId)) names.push(descendant.name);
|
|
91
|
+
}
|
|
92
|
+
if (isPresent(names)) nested.set(row.id, names);
|
|
93
|
+
});
|
|
94
|
+
return nested;
|
|
95
|
+
}
|
|
96
|
+
|
|
82
97
|
export function hiddenRowCount(
|
|
83
98
|
rows: DatastoreRow[],
|
|
84
99
|
visibility: DatastoreRowVisibility,
|