@manta-eu/react 0.2.0 → 0.2.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/dist/components/channel-icon/index.d.ts +1 -0
- package/dist/components/channel-icon/index.d.ts.map +1 -1
- package/dist/components/channel-icon/index.js +1 -1
- package/dist/components/channel-icon/index.js.map +1 -1
- package/dist/components/planning/ActioningView.d.ts +1 -2
- package/dist/components/planning/ActioningView.d.ts.map +1 -1
- package/dist/components/planning/ActioningView.js +19 -44
- package/dist/components/planning/ActioningView.js.map +1 -1
- package/dist/components/planning/ManualActionCard.d.ts +4 -0
- package/dist/components/planning/ManualActionCard.d.ts.map +1 -1
- package/dist/components/planning/ManualActionCard.js +7 -5
- package/dist/components/planning/ManualActionCard.js.map +1 -1
- package/dist/components/planning/ManualActionList.d.ts +4 -0
- package/dist/components/planning/ManualActionList.d.ts.map +1 -1
- package/dist/components/planning/ManualActionList.js +8 -2
- package/dist/components/planning/ManualActionList.js.map +1 -1
- package/dist/components/planning/actioning-view-utils.d.ts +9 -0
- package/dist/components/planning/actioning-view-utils.d.ts.map +1 -1
- package/dist/components/planning/actioning-view-utils.js +19 -1
- package/dist/components/planning/actioning-view-utils.js.map +1 -1
- package/dist/components/tooltip/index.d.ts +7 -0
- package/dist/components/tooltip/index.d.ts.map +1 -1
- package/dist/components/tooltip/index.js +10 -1
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/globals.css +19 -6
- package/dist/graph/entity-link.d.ts +14 -0
- package/dist/graph/entity-link.d.ts.map +1 -0
- package/dist/graph/entity-link.js +12 -0
- package/dist/graph/entity-link.js.map +1 -0
- package/dist/graph/graph-list-utils.d.ts +1 -0
- package/dist/graph/graph-list-utils.d.ts.map +1 -1
- package/dist/graph/graph-list-utils.js +1 -0
- package/dist/graph/graph-list-utils.js.map +1 -1
- package/dist/graph/graph-model.d.ts.map +1 -1
- package/dist/graph/graph-model.js +14 -5
- package/dist/graph/graph-model.js.map +1 -1
- package/dist/graph/nodes/BrandAssetAnalysisView.d.ts.map +1 -1
- package/dist/graph/nodes/BrandAssetAnalysisView.js +2 -1
- package/dist/graph/nodes/BrandAssetAnalysisView.js.map +1 -1
- package/dist/graph/nodes/BrandFontSuggestionView.d.ts.map +1 -1
- package/dist/graph/nodes/BrandFontSuggestionView.js +2 -1
- package/dist/graph/nodes/BrandFontSuggestionView.js.map +1 -1
- package/dist/graph/nodes/BrandGuidelinesView.js +1 -1
- package/dist/graph/nodes/BrandGuidelinesView.js.map +1 -1
- package/dist/graph/nodes/NodePropertyTable.d.ts +4 -2
- package/dist/graph/nodes/NodePropertyTable.d.ts.map +1 -1
- package/dist/graph/nodes/NodePropertyTable.js +12 -59
- package/dist/graph/nodes/NodePropertyTable.js.map +1 -1
- package/dist/graph/nodes/node-property-row.d.ts +30 -0
- package/dist/graph/nodes/node-property-row.d.ts.map +1 -0
- package/dist/graph/nodes/node-property-row.js +35 -0
- package/dist/graph/nodes/node-property-row.js.map +1 -0
- package/dist/graph/property-formatters.d.ts +1 -0
- package/dist/graph/property-formatters.d.ts.map +1 -1
- package/dist/graph/property-formatters.js +4 -1
- package/dist/graph/property-formatters.js.map +1 -1
- package/dist/graph/special-nodes/AttributeList.d.ts +27 -0
- package/dist/graph/special-nodes/AttributeList.d.ts.map +1 -0
- package/dist/graph/special-nodes/AttributeList.js +57 -0
- package/dist/graph/special-nodes/AttributeList.js.map +1 -0
- package/dist/graph/special-nodes/EntityPreview.d.ts +5 -0
- package/dist/graph/special-nodes/EntityPreview.d.ts.map +1 -0
- package/dist/graph/special-nodes/EntityPreview.js +19 -0
- package/dist/graph/special-nodes/EntityPreview.js.map +1 -0
- package/dist/graph/special-nodes/EntityValueChip.d.ts +11 -0
- package/dist/graph/special-nodes/EntityValueChip.d.ts.map +1 -0
- package/dist/graph/special-nodes/EntityValueChip.js +40 -0
- package/dist/graph/special-nodes/EntityValueChip.js.map +1 -0
- package/dist/graph/special-nodes/GenericView.d.ts +2 -1
- package/dist/graph/special-nodes/GenericView.d.ts.map +1 -1
- package/dist/graph/special-nodes/GenericView.js +23 -69
- package/dist/graph/special-nodes/GenericView.js.map +1 -1
- package/dist/graph/special-nodes/card-facts.d.ts +52 -0
- package/dist/graph/special-nodes/card-facts.d.ts.map +1 -0
- package/dist/graph/special-nodes/card-facts.js +189 -0
- package/dist/graph/special-nodes/card-facts.js.map +1 -0
- package/dist/graph/special-nodes/property-icons.d.ts +9 -0
- package/dist/graph/special-nodes/property-icons.d.ts.map +1 -0
- package/dist/graph/special-nodes/property-icons.js +39 -0
- package/dist/graph/special-nodes/property-icons.js.map +1 -0
- package/dist/graph/status-utils.d.ts +16 -0
- package/dist/graph/status-utils.d.ts.map +1 -1
- package/dist/graph/status-utils.js +35 -0
- package/dist/graph/status-utils.js.map +1 -1
- package/dist/graph/types.d.ts +5 -0
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/hooks/use-approve-anyway.d.ts +12 -0
- package/dist/hooks/use-approve-anyway.d.ts.map +1 -0
- package/dist/hooks/use-approve-anyway.js +81 -0
- package/dist/hooks/use-approve-anyway.js.map +1 -0
- package/dist/hooks/use-connection-problems.d.ts.map +1 -1
- package/dist/hooks/useValidateActionPlan.d.ts.map +1 -1
- package/dist/ldo-ui.d.ts +2 -1
- package/dist/ldo-ui.d.ts.map +1 -1
- package/dist/ldo-ui.js +1 -0
- package/dist/ldo-ui.js.map +1 -1
- package/dist/lib/manual-actions.d.ts +2 -1
- package/dist/lib/manual-actions.d.ts.map +1 -1
- package/dist/lib/manual-actions.js +3 -2
- package/dist/lib/manual-actions.js.map +1 -1
- package/dist/providers/ActiveChatProvider.js +3 -1
- package/dist/providers/ActiveChatProvider.js.map +1 -1
- package/dist/providers/ChatManagerProvider.d.ts.map +1 -1
- package/dist/providers/ChatManagerProvider.js +3 -1
- package/dist/providers/ChatManagerProvider.js.map +1 -1
- package/dist/providers/chat-socket-auth.d.ts +7 -2
- package/dist/providers/chat-socket-auth.d.ts.map +1 -1
- package/dist/providers/chat-socket-auth.js +87 -16
- package/dist/providers/chat-socket-auth.js.map +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +8 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/routes/activity/detail/ActivityChanges.d.ts +2 -0
- package/dist/routes/activity/detail/ActivityChanges.d.ts.map +1 -1
- package/dist/routes/activity/detail/ActivityChanges.js +8 -7
- package/dist/routes/activity/detail/ActivityChanges.js.map +1 -1
- package/dist/routes/activity/detail/ActivityDetail.d.ts +1 -0
- package/dist/routes/activity/detail/ActivityDetail.d.ts.map +1 -1
- package/dist/routes/activity/detail/ActivityDetail.js +5 -4
- package/dist/routes/activity/detail/ActivityDetail.js.map +1 -1
- package/dist/routes/activity/detail/ActivityStepsRail.d.ts +1 -0
- package/dist/routes/activity/detail/ActivityStepsRail.d.ts.map +1 -1
- package/dist/routes/activity/detail/ActivityStepsRail.js +3 -2
- package/dist/routes/activity/detail/ActivityStepsRail.js.map +1 -1
- package/dist/routes/activity/detail/ActivityTimeline.js +2 -2
- package/dist/routes/activity/detail/ActivityTimeline.js.map +1 -1
- package/dist/routes/activity/detail/NoChangesMessage.d.ts +6 -0
- package/dist/routes/activity/detail/NoChangesMessage.d.ts.map +1 -0
- package/dist/routes/activity/detail/NoChangesMessage.js +10 -0
- package/dist/routes/activity/detail/NoChangesMessage.js.map +1 -0
- package/dist/routes/data-explorer/components/RawTriples.js +1 -1
- package/dist/routes/data-explorer/components/RawTriples.js.map +1 -1
- package/dist/routes/data-explorer/components/RecentTypes.js +1 -1
- package/dist/routes/data-explorer/components/RecentTypes.js.map +1 -1
- package/dist/routes/data-explorer/components/class-detail/Instances.js +1 -1
- package/dist/routes/data-explorer/components/class-detail/Instances.js.map +1 -1
- package/dist/routes/data-explorer/components/namespace-detail/NamespaceClassRow.js +1 -1
- package/dist/routes/data-explorer/components/namespace-detail/NamespaceClassRow.js.map +1 -1
- package/dist/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.js +1 -1
- package/dist/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.js.map +1 -1
- package/dist/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.js +1 -1
- package/dist/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.js.map +1 -1
- package/dist/routes/data-explorer/components/record-detail/ChatPreview.js +2 -2
- package/dist/routes/data-explorer/components/record-detail/ChatPreview.js.map +1 -1
- package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.d.ts.map +1 -1
- package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.js +13 -6
- package/dist/routes/integrations/components/connect-forms/DataConnectionConnectForm.js.map +1 -1
- package/dist/routes/integrations/components/connect-forms/data-connection-fields.d.ts +3 -0
- package/dist/routes/integrations/components/connect-forms/data-connection-fields.d.ts.map +1 -1
- package/dist/routes/integrations/components/connect-forms/data-connection-fields.js +9 -2
- package/dist/routes/integrations/components/connect-forms/data-connection-fields.js.map +1 -1
- package/dist/routes/plans/components/ActionPlanPage.d.ts.map +1 -1
- package/dist/routes/plans/components/ActionPlanPage.js +4 -7
- package/dist/routes/plans/components/ActionPlanPage.js.map +1 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.d.ts.map +1 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.js +2 -1
- package/dist/routes/plans/components/PlanDetailRouteContent.js.map +1 -1
- package/package.json +12 -7
- package/src/app-subpaths.json +1 -0
- package/src/components/channel-icon/index.tsx +1 -1
- package/src/components/planning/ActioningView.tsx +62 -70
- package/src/components/planning/ManualActionCard.tsx +66 -14
- package/src/components/planning/ManualActionList.tsx +30 -11
- package/src/components/planning/actioning-view-utils.ts +27 -1
- package/src/components/tooltip/index.tsx +35 -2
- package/src/graph/entity-link.tsx +20 -0
- package/src/graph/graph-list-utils.ts +2 -0
- package/src/graph/graph-model.ts +15 -6
- package/src/graph/nodes/BrandAssetAnalysisView.tsx +3 -1
- package/src/graph/nodes/BrandFontSuggestionView.tsx +7 -1
- package/src/graph/nodes/BrandGuidelinesView.tsx +1 -1
- package/src/graph/nodes/NodePropertyTable.tsx +32 -143
- package/src/graph/nodes/node-property-row.tsx +154 -0
- package/src/graph/property-formatters.ts +5 -1
- package/src/graph/special-nodes/AttributeList.tsx +177 -0
- package/src/graph/special-nodes/EntityPreview.tsx +27 -0
- package/src/graph/special-nodes/EntityValueChip.tsx +85 -0
- package/src/graph/special-nodes/GenericView.tsx +49 -139
- package/src/graph/special-nodes/card-facts.ts +278 -0
- package/src/graph/special-nodes/property-icons.ts +59 -0
- package/src/graph/status-utils.ts +40 -0
- package/src/graph/types.ts +4 -0
- package/src/hooks/use-approve-anyway.ts +100 -0
- package/src/ldo-ui.ts +8 -0
- package/src/lib/manual-actions.ts +3 -2
- package/src/providers/ActiveChatProvider.tsx +3 -1
- package/src/providers/ChatManagerProvider.tsx +3 -1
- package/src/providers/chat-socket-auth.ts +95 -16
- package/src/providers/index.tsx +16 -1
- package/src/routes/activity/detail/ActivityChanges.tsx +21 -5
- package/src/routes/activity/detail/ActivityDetail.tsx +24 -5
- package/src/routes/activity/detail/ActivityStepsRail.tsx +4 -2
- package/src/routes/activity/detail/ActivityTimeline.tsx +2 -2
- package/src/routes/activity/detail/NoChangesMessage.tsx +20 -0
- package/src/routes/data-explorer/components/RawTriples.tsx +1 -1
- package/src/routes/data-explorer/components/RecentTypes.tsx +1 -1
- package/src/routes/data-explorer/components/class-detail/Instances.tsx +1 -1
- package/src/routes/data-explorer/components/namespace-detail/NamespaceClassRow.tsx +1 -1
- package/src/routes/data-explorer/components/ontology-coverage/CoverageTermColumns.tsx +1 -1
- package/src/routes/data-explorer/components/ontology-coverage/UnobservedRuleVocabulary.tsx +1 -1
- package/src/routes/data-explorer/components/record-detail/ChatPreview.tsx +2 -2
- package/src/routes/integrations/components/connect-forms/DataConnectionConnectForm.tsx +38 -12
- package/src/routes/integrations/components/connect-forms/data-connection-fields.ts +11 -2
- package/src/routes/plans/components/ActionPlanPage.tsx +9 -8
- package/src/routes/plans/components/PlanDetailRouteContent.tsx +4 -3
- package/dist/components/planning/ExecutionActivityRow.d.ts +0 -9
- package/dist/components/planning/ExecutionActivityRow.d.ts.map +0 -1
- package/dist/components/planning/ExecutionActivityRow.js +0 -53
- package/dist/components/planning/ExecutionActivityRow.js.map +0 -1
- package/dist/components/planning/ExecutionSectionItem.d.ts +0 -20
- package/dist/components/planning/ExecutionSectionItem.d.ts.map +0 -1
- package/dist/components/planning/ExecutionSectionItem.js +0 -66
- package/dist/components/planning/ExecutionSectionItem.js.map +0 -1
- package/dist/components/planning/ExecutionSections.d.ts +0 -16
- package/dist/components/planning/ExecutionSections.d.ts.map +0 -1
- package/dist/components/planning/ExecutionSections.js +0 -32
- package/dist/components/planning/ExecutionSections.js.map +0 -1
- package/dist/components/planning/action-plan-icons.d.ts +0 -3
- package/dist/components/planning/action-plan-icons.d.ts.map +0 -1
- package/dist/components/planning/action-plan-icons.js +0 -25
- package/dist/components/planning/action-plan-icons.js.map +0 -1
- package/dist/components/planning/execution-section-model.d.ts +0 -24
- package/dist/components/planning/execution-section-model.d.ts.map +0 -1
- package/dist/components/planning/execution-section-model.js +0 -150
- package/dist/components/planning/execution-section-model.js.map +0 -1
- package/dist/routes/data-explorer/components/TruncateTooltip.d.ts +0 -7
- package/dist/routes/data-explorer/components/TruncateTooltip.d.ts.map +0 -1
- package/dist/routes/data-explorer/components/TruncateTooltip.js +0 -9
- package/dist/routes/data-explorer/components/TruncateTooltip.js.map +0 -1
- package/src/components/planning/ExecutionActivityRow.tsx +0 -89
- package/src/components/planning/ExecutionSectionItem.tsx +0 -184
- package/src/components/planning/ExecutionSections.tsx +0 -97
- package/src/components/planning/action-plan-icons.tsx +0 -27
- package/src/components/planning/execution-section-model.ts +0 -190
- package/src/routes/data-explorer/components/TruncateTooltip.tsx +0 -25
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { isFullIri } from "@manta-eu/client/shared/rdf-namespaces";
|
|
2
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
3
|
+
import { Search } from "lucide-react";
|
|
4
|
+
import { useMemo, useState } from "react";
|
|
5
|
+
import type { PreviewFieldHints } from "../graph-list-utils";
|
|
6
|
+
import { NodePropertyExpander, NodePropertyList, NodePropertyRow, PropertyValue } from "../nodes/node-property-row";
|
|
7
|
+
import { ontologyTermIrisForProperties, withOntologyLabels } from "../ontology-labels";
|
|
8
|
+
import type { NodeProperty, NodeRelation } from "../types";
|
|
9
|
+
import { buildCardFacts, type CardFact, isAttributeSearchable, searchFacts, visibleChips } from "./card-facts";
|
|
10
|
+
import { EntityValueChip } from "./EntityValueChip";
|
|
11
|
+
import { ChannelIcon, channelDisplayName } from "#components/channel-icon/index";
|
|
12
|
+
import { useOntologyTerms } from "#hooks/use-ontology-terms";
|
|
13
|
+
import { cn } from "#style";
|
|
14
|
+
|
|
15
|
+
export type UseAttributeFactsArgs = {
|
|
16
|
+
properties: NodeProperty[];
|
|
17
|
+
relations?: NodeRelation[];
|
|
18
|
+
jsonLd: Record<string, unknown>;
|
|
19
|
+
title?: string;
|
|
20
|
+
type?: string;
|
|
21
|
+
prefixLabels?: boolean;
|
|
22
|
+
previewHints?: PreviewFieldHints;
|
|
23
|
+
// Applied after ontology labels resolve, so a filter can match on the label too. Keep it stable (useCallback).
|
|
24
|
+
include?: (property: NodeProperty) => boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function useAttributeFacts(args: UseAttributeFactsArgs): { facts: CardFact[]; previewCount: number } {
|
|
28
|
+
const { properties, jsonLd, title, type, prefixLabels, previewHints, include } = args;
|
|
29
|
+
|
|
30
|
+
const relations = useMemo(() => args.relations ?? [], [args.relations]);
|
|
31
|
+
|
|
32
|
+
const terms = useOntologyTerms(
|
|
33
|
+
useMemo(
|
|
34
|
+
() => [
|
|
35
|
+
...ontologyTermIrisForProperties(properties),
|
|
36
|
+
...relations.flatMap((relation) => [relation.predicate, relation.target]).filter(isFullIri),
|
|
37
|
+
],
|
|
38
|
+
[properties, relations],
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return useMemo(() => {
|
|
43
|
+
const labeled = withOntologyLabels(properties, terms);
|
|
44
|
+
|
|
45
|
+
return buildCardFacts({
|
|
46
|
+
properties: include ? labeled.filter(include) : labeled,
|
|
47
|
+
relations,
|
|
48
|
+
jsonLd,
|
|
49
|
+
terms,
|
|
50
|
+
title,
|
|
51
|
+
type,
|
|
52
|
+
prefixLabels,
|
|
53
|
+
previewHints,
|
|
54
|
+
});
|
|
55
|
+
}, [properties, terms, include, relations, jsonLd, title, type, prefixLabels, previewHints]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type EntityChipsProps = { iris: string[]; color?: string; expanded: boolean };
|
|
59
|
+
|
|
60
|
+
function EntityChips(props: EntityChipsProps) {
|
|
61
|
+
const { iris, color, expanded } = props;
|
|
62
|
+
|
|
63
|
+
const { shown, hiddenCount } = visibleChips(iris, expanded);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<span className="flex min-w-0 flex-wrap items-center gap-1">
|
|
67
|
+
{shown.map((iri) => (
|
|
68
|
+
<EntityValueChip key={iri} iri={iri} color={color} />
|
|
69
|
+
))}
|
|
70
|
+
{hiddenCount > 0 && <span className="typography-tiny text-muted-foreground">+{hiddenCount}</span>}
|
|
71
|
+
</span>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type AttributeListProps = {
|
|
76
|
+
facts: CardFact[];
|
|
77
|
+
previewCount: number;
|
|
78
|
+
searchClassName?: string;
|
|
79
|
+
bodyClassName?: string;
|
|
80
|
+
// Lists every attribute with no preview, so a card that promises the whole record keeps that promise.
|
|
81
|
+
showAll?: boolean;
|
|
82
|
+
defaultExpanded?: boolean;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function AttributeList(props: AttributeListProps) {
|
|
86
|
+
const { facts, previewCount, searchClassName, bodyClassName, showAll = false, defaultExpanded = false } = props;
|
|
87
|
+
|
|
88
|
+
const [expandedByUser, setExpanded] = useState(defaultExpanded);
|
|
89
|
+
const expanded = showAll || expandedByUser;
|
|
90
|
+
const [query, setQuery] = useState("");
|
|
91
|
+
|
|
92
|
+
const searchable = isAttributeSearchable(expanded, facts.length);
|
|
93
|
+
const searching = searchable && isPresent(query.trim());
|
|
94
|
+
const matchingFacts = useMemo(
|
|
95
|
+
() => (searching ? searchFacts(facts, query.trim()) : facts),
|
|
96
|
+
[searching, facts, query],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const visibleFacts = searching || expanded ? matchingFacts : facts.slice(0, previewCount);
|
|
100
|
+
const hasHiddenChips = visibleFacts.some((fact) => visibleChips(fact.entityIris, expanded).hiddenCount > 0);
|
|
101
|
+
const hiddenCount = facts.length - previewCount;
|
|
102
|
+
|
|
103
|
+
if (!isPresent(facts)) return null;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="flex flex-col">
|
|
107
|
+
{searchable && (
|
|
108
|
+
<label
|
|
109
|
+
className={cn(
|
|
110
|
+
"nodrag nopan flex items-center gap-2 border-b border-muted-border py-2 text-muted-foreground",
|
|
111
|
+
searchClassName,
|
|
112
|
+
)}
|
|
113
|
+
>
|
|
114
|
+
<Search aria-hidden className="size-3.5 shrink-0" />
|
|
115
|
+
<input
|
|
116
|
+
type="search"
|
|
117
|
+
value={query}
|
|
118
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
119
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
120
|
+
placeholder="Search attributes…"
|
|
121
|
+
aria-label="Search attributes"
|
|
122
|
+
className="typography-tiny min-w-0 flex-1 bg-transparent text-default-foreground outline-none placeholder:text-muted-foreground"
|
|
123
|
+
/>
|
|
124
|
+
</label>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
<div className={cn("flex flex-col", bodyClassName)}>
|
|
128
|
+
<NodePropertyList>
|
|
129
|
+
{visibleFacts.map(({ key, label, value, display, icon, entityIris, entityColor, highlighted, channel }) => (
|
|
130
|
+
<NodePropertyRow
|
|
131
|
+
key={key}
|
|
132
|
+
label={label}
|
|
133
|
+
icon={channel ? undefined : icon}
|
|
134
|
+
labelPrefix={
|
|
135
|
+
channel && (
|
|
136
|
+
<span
|
|
137
|
+
role="img"
|
|
138
|
+
aria-label={channelDisplayName[channel]}
|
|
139
|
+
title={channelDisplayName[channel]}
|
|
140
|
+
className="shrink-0 self-center"
|
|
141
|
+
>
|
|
142
|
+
<ChannelIcon channel={channel} iconClassName="size-3" />
|
|
143
|
+
</span>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
overflow={isPresent(entityIris) ? "wrap" : expanded && display.kind === "prose" ? "clamp" : "truncate"}
|
|
147
|
+
valueTitle={value}
|
|
148
|
+
divided={false}
|
|
149
|
+
wideLabel
|
|
150
|
+
emphasized={highlighted}
|
|
151
|
+
valueClassName={cn("tabular-nums", highlighted && "text-primary")}
|
|
152
|
+
>
|
|
153
|
+
{isPresent(entityIris) ? (
|
|
154
|
+
<EntityChips iris={entityIris} color={entityColor} expanded={expanded} />
|
|
155
|
+
) : (
|
|
156
|
+
<PropertyValue display={display} />
|
|
157
|
+
)}
|
|
158
|
+
</NodePropertyRow>
|
|
159
|
+
))}
|
|
160
|
+
</NodePropertyList>
|
|
161
|
+
{searching && !isPresent(visibleFacts) && (
|
|
162
|
+
<span className="typography-tiny py-1 text-muted-foreground">No matching attributes.</span>
|
|
163
|
+
)}
|
|
164
|
+
{!showAll && (hiddenCount > 0 || hasHiddenChips || expanded) && (
|
|
165
|
+
<NodePropertyExpander
|
|
166
|
+
expanded={expanded}
|
|
167
|
+
collapsedLabel={hiddenCount > 0 ? `Show all ${facts.length}` : "Show all"}
|
|
168
|
+
onToggle={() => {
|
|
169
|
+
setExpanded((value) => !value);
|
|
170
|
+
setQuery("");
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { stubNodeData } from "../stub-node";
|
|
3
|
+
import { InsideEntityPreview } from "./EntityValueChip";
|
|
4
|
+
import { GenericView } from "./GenericView";
|
|
5
|
+
import { getEntityView } from "./router";
|
|
6
|
+
import { useEntityNodes } from "#hooks/use-entity-nodes";
|
|
7
|
+
|
|
8
|
+
export type EntityPreviewProps = { iri: string };
|
|
9
|
+
|
|
10
|
+
// Loaded lazily from EntityValueChip: a static import would cycle through the node router back to GenericView.
|
|
11
|
+
export function EntityPreview(props: EntityPreviewProps) {
|
|
12
|
+
const { iri } = props;
|
|
13
|
+
|
|
14
|
+
const iris = useMemo(() => [iri], [iri]);
|
|
15
|
+
const { nodesByIri, isLoading } = useEntityNodes(iris);
|
|
16
|
+
|
|
17
|
+
if (isLoading) return <div className="typography-tiny px-3 py-2 text-muted-foreground">Loading…</div>;
|
|
18
|
+
|
|
19
|
+
const node = nodesByIri.get(iri);
|
|
20
|
+
const View = (node && getEntityView(node.data.type, node.data.actionShape)) ?? GenericView;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<InsideEntityPreview.Provider value={true}>
|
|
24
|
+
<View data={node?.data ?? stubNodeData(iri)} />
|
|
25
|
+
</InsideEntityPreview.Provider>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { humanizeEntityName } from "@manta-eu/client/shared/agent-mode";
|
|
2
|
+
import { iriLocalName } from "@manta-eu/client/shared/rdf-namespaces";
|
|
3
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
4
|
+
import { createContext, lazy, Suspense, useContext } from "react";
|
|
5
|
+
import { useEntityLink } from "../entity-link";
|
|
6
|
+
import { iriChannel } from "./card-facts";
|
|
7
|
+
import { ChannelIcon, channelDisplayName } from "#components/channel-icon/index";
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "#components/tooltip/index";
|
|
9
|
+
import { useInstanceLabel } from "#hooks/use-instance-labels";
|
|
10
|
+
import { useOntologyTerm } from "#hooks/use-ontology-terms";
|
|
11
|
+
import { cn } from "#style";
|
|
12
|
+
|
|
13
|
+
const EntityPreview = lazy(() => import("./EntityPreview").then((module) => ({ default: module.EntityPreview })));
|
|
14
|
+
|
|
15
|
+
// A preview already shows one entity card; its own chips skip the hover so previews never stack.
|
|
16
|
+
export const InsideEntityPreview = createContext(false);
|
|
17
|
+
|
|
18
|
+
// An ontology term (e.g. an enum concept) has a label but no record of its own to open or preview.
|
|
19
|
+
export function useEntityValueLabel(iri: string): { label: string; isOntologyTerm: boolean } {
|
|
20
|
+
const term = useOntologyTerm(iri);
|
|
21
|
+
const instanceLabel = useInstanceLabel(term?.label ? undefined : iri);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
label: term?.label ?? instanceLabel ?? humanizeEntityName(iriLocalName(iri)),
|
|
25
|
+
isOntologyTerm: isPresent(term?.label),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type EntityValueChipProps = { iri: string; color?: string };
|
|
30
|
+
|
|
31
|
+
export function EntityValueChip(props: EntityValueChipProps) {
|
|
32
|
+
const { iri, color } = props;
|
|
33
|
+
|
|
34
|
+
const { label, isOntologyTerm } = useEntityValueLabel(iri);
|
|
35
|
+
const EntityLink = useEntityLink();
|
|
36
|
+
const insidePreview = useContext(InsideEntityPreview);
|
|
37
|
+
const channel = iriChannel(iri);
|
|
38
|
+
|
|
39
|
+
if (isOntologyTerm) return <span className="min-w-0 truncate">{label}</span>;
|
|
40
|
+
|
|
41
|
+
const chipClassName = cn(
|
|
42
|
+
"nodrag inline-flex min-w-0 max-w-full items-center gap-1.5 rounded bg-faint px-1.5 py-px text-default-foreground",
|
|
43
|
+
EntityLink && "transition-colors hover:bg-accent hover:text-primary",
|
|
44
|
+
);
|
|
45
|
+
const content = (
|
|
46
|
+
<>
|
|
47
|
+
{channel ? (
|
|
48
|
+
<span role="img" aria-label={channelDisplayName[channel]} className="shrink-0">
|
|
49
|
+
<ChannelIcon channel={channel} iconClassName="size-3" />
|
|
50
|
+
</span>
|
|
51
|
+
) : (
|
|
52
|
+
color && <span aria-hidden className="size-1.5 shrink-0 rounded-full" style={{ backgroundColor: color }} />
|
|
53
|
+
)}
|
|
54
|
+
<span className="min-w-0 truncate">{label}</span>
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
const chip = EntityLink ? (
|
|
58
|
+
<EntityLink iri={iri} className={chipClassName}>
|
|
59
|
+
{content}
|
|
60
|
+
</EntityLink>
|
|
61
|
+
) : (
|
|
62
|
+
<span className={chipClassName}>{content}</span>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (insidePreview) return chip;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Tooltip>
|
|
69
|
+
<TooltipTrigger asChild>
|
|
70
|
+
<span className="inline-flex min-w-0 max-w-full">{chip}</span>
|
|
71
|
+
</TooltipTrigger>
|
|
72
|
+
<TooltipContent
|
|
73
|
+
side="bottom"
|
|
74
|
+
align="start"
|
|
75
|
+
collisionPadding={8}
|
|
76
|
+
showArrow={false}
|
|
77
|
+
className="rounded-lg-v4 border border-default-border bg-card p-1.5 shadow-v4"
|
|
78
|
+
>
|
|
79
|
+
<Suspense fallback={null}>
|
|
80
|
+
<EntityPreview iri={iri} />
|
|
81
|
+
</Suspense>
|
|
82
|
+
</TooltipContent>
|
|
83
|
+
</Tooltip>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -1,47 +1,24 @@
|
|
|
1
|
-
import { isPresent } from "@manta-eu/lib/present";
|
|
2
|
-
import { expandPrefix } from "@manta-eu/rdf-lib/namespaces";
|
|
3
1
|
import { ChevronRight } from "lucide-react";
|
|
4
2
|
import { memo, useCallback, useMemo } from "react";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from "../graph-list-utils";
|
|
11
|
-
import { ontologyTermIrisForProperties, withOntologyLabels } from "../ontology-labels";
|
|
12
|
-
import { rdfTypeColors } from "../rdf-type-colors";
|
|
13
|
-
import { statusColors } from "../status-utils";
|
|
14
|
-
import { useGraphTheme } from "../themes";
|
|
15
|
-
import type { EntityViewProps } from "../types";
|
|
3
|
+
import type { PreviewFieldHints } from "../graph-list-utils";
|
|
4
|
+
import { statusBadgeVariant } from "../status-utils";
|
|
5
|
+
import type { EntityViewProps, NodeProperty } from "../types";
|
|
6
|
+
import { AttributeList, useAttributeFacts } from "./AttributeList";
|
|
7
|
+
import { typeColor } from "./card-facts";
|
|
16
8
|
import { getStatus } from "./node-data";
|
|
17
|
-
import {
|
|
9
|
+
import { Badge } from "#components/badge/index";
|
|
18
10
|
import { cn } from "#style";
|
|
19
11
|
|
|
20
|
-
const defaultColor = "#6b7280";
|
|
21
|
-
const explainCardMaxFacts = 5;
|
|
22
|
-
|
|
23
|
-
type CardFact = { label: string; value: string; highlighted: boolean };
|
|
24
|
-
|
|
25
|
-
function getNodeColor(type?: string): string {
|
|
26
|
-
if (!type) return defaultColor;
|
|
27
|
-
return rdfTypeColors[type] ?? rdfTypeColors[expandPrefix(type)] ?? defaultColor;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getStatusStyle(status: string): { bg: string; text: string } {
|
|
31
|
-
return statusColors[status.toLowerCase()] ?? { bg: "#94a3b8", text: "#ffffff" };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
12
|
export type GenericViewProps = EntityViewProps & {
|
|
35
13
|
previewHints?: PreviewFieldHints;
|
|
36
14
|
onToggleCollapse?: () => void;
|
|
15
|
+
defaultExpanded?: boolean;
|
|
37
16
|
};
|
|
38
17
|
|
|
39
18
|
export const GenericView = memo(function GenericView(props: GenericViewProps) {
|
|
40
|
-
const { data, selected = false, onContextMenu, previewHints, onToggleCollapse } = props;
|
|
41
|
-
|
|
42
|
-
const theme = useGraphTheme();
|
|
19
|
+
const { data, selected = false, onContextMenu, previewHints, onToggleCollapse, defaultExpanded } = props;
|
|
43
20
|
|
|
44
|
-
const color = useMemo(() =>
|
|
21
|
+
const color = useMemo(() => typeColor(data.type), [data.type]);
|
|
45
22
|
const hasChildren = data.hasChildren;
|
|
46
23
|
const isCollapsed = data.isCollapsed;
|
|
47
24
|
|
|
@@ -55,137 +32,70 @@ export const GenericView = memo(function GenericView(props: GenericViewProps) {
|
|
|
55
32
|
|
|
56
33
|
const status = useMemo(() => getStatus(data), [data]);
|
|
57
34
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const total = properties.filter((prop) => isDisplayablePreviewKey(prop.key)).length;
|
|
77
|
-
|
|
78
|
-
const facts: CardFact[] = fields.map((field) => ({
|
|
79
|
-
label: field.label,
|
|
80
|
-
value: field.value,
|
|
81
|
-
highlighted: field.isHighlighted ?? false,
|
|
82
|
-
}));
|
|
83
|
-
|
|
84
|
-
return { displayProperties: facts, hiddenPropertiesCount: Math.max(0, total - facts.length) };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const importantKeys = ["name", "title", "label", "description", "id"];
|
|
88
|
-
const important: typeof properties = [];
|
|
89
|
-
const other: typeof properties = [];
|
|
90
|
-
|
|
91
|
-
for (const prop of properties) {
|
|
92
|
-
const keyLower = prop.key.toLowerCase();
|
|
93
|
-
if (importantKeys.some((k) => keyLower.includes(k))) {
|
|
94
|
-
important.push(prop);
|
|
95
|
-
} else {
|
|
96
|
-
other.push(prop);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const maxProperties = 4;
|
|
101
|
-
const allDisplayed: CardFact[] = [...important, ...other]
|
|
102
|
-
.slice(0, maxProperties)
|
|
103
|
-
.map((prop) => ({ label: prop.label ?? prop.key, value: prop.value, highlighted: false }));
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
displayProperties: allDisplayed,
|
|
107
|
-
hiddenPropertiesCount: Math.max(0, important.length + other.length - maxProperties),
|
|
108
|
-
};
|
|
109
|
-
}, [labeledProperties, status, previewHints]);
|
|
35
|
+
const include = useCallback((property: NodeProperty) => !status || property.key !== status.key, [status]);
|
|
36
|
+
// The header badge shows the status, so an IRI-valued one must not come back as a relation row either.
|
|
37
|
+
const relations = useMemo(() => {
|
|
38
|
+
const statusIri = status ? data.properties.find((property) => property.key === status.key)?.iri : undefined;
|
|
39
|
+
return statusIri ? data.relations?.filter((relation) => relation.predicate !== statusIri) : data.relations;
|
|
40
|
+
}, [status, data.properties, data.relations]);
|
|
41
|
+
|
|
42
|
+
const { facts, previewCount } = useAttributeFacts({
|
|
43
|
+
properties: data.properties,
|
|
44
|
+
relations,
|
|
45
|
+
jsonLd: data.jsonLd,
|
|
46
|
+
title: data.label,
|
|
47
|
+
type: data.type,
|
|
48
|
+
prefixLabels: true,
|
|
49
|
+
previewHints,
|
|
50
|
+
include,
|
|
51
|
+
});
|
|
110
52
|
|
|
111
53
|
return (
|
|
112
54
|
<div
|
|
113
|
-
className=
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
borderWidth: 2,
|
|
118
|
-
borderStyle: "solid",
|
|
119
|
-
...(selected && { boxShadow: `0 0 0 2px ${theme.node.selectedRing}` }),
|
|
120
|
-
}}
|
|
55
|
+
className={cn(
|
|
56
|
+
"min-w-60 max-w-80 overflow-hidden rounded-md-v4 border border-muted-border bg-muted text-default-foreground transition-shadow duration-200",
|
|
57
|
+
selected && "ring-2 ring-active-border",
|
|
58
|
+
)}
|
|
121
59
|
onContextMenu={onContextMenu}
|
|
122
60
|
>
|
|
123
|
-
<div className="
|
|
124
|
-
<div className="flex items-center gap-2
|
|
61
|
+
<div className="flex items-center justify-between gap-2 border-b border-muted-border px-3 py-2">
|
|
62
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
125
63
|
{hasChildren && onToggleCollapse && (
|
|
126
64
|
<button
|
|
65
|
+
type="button"
|
|
127
66
|
onClick={handleToggleClick}
|
|
128
|
-
className=
|
|
129
|
-
"w-5 h-5 rounded flex items-center justify-center shrink-0",
|
|
130
|
-
"bg-muted",
|
|
131
|
-
"hover:bg-muted transition-all duration-200",
|
|
132
|
-
)}
|
|
133
|
-
style={{ color: theme.node.text }}
|
|
67
|
+
className="nodrag flex size-5 shrink-0 items-center justify-center rounded bg-accent text-muted-foreground transition-colors hover:text-default-foreground"
|
|
134
68
|
title={isCollapsed ? "Expand" : "Collapse"}
|
|
135
69
|
>
|
|
136
|
-
<ChevronRight className={cn("
|
|
70
|
+
<ChevronRight className={cn("size-3 transition-transform duration-200", !isCollapsed && "rotate-90")} />
|
|
137
71
|
</button>
|
|
138
72
|
)}
|
|
139
|
-
<
|
|
73
|
+
<span aria-hidden className="size-2 shrink-0 rounded-full" style={{ backgroundColor: color }} />
|
|
74
|
+
<div className="typography-small-medium truncate" title={data.label}>
|
|
140
75
|
{data.label}
|
|
141
76
|
</div>
|
|
142
77
|
</div>
|
|
143
|
-
<div className="flex items-center gap-1.5
|
|
78
|
+
<div className="flex shrink-0 items-center gap-1.5">
|
|
144
79
|
{status && (
|
|
145
|
-
<
|
|
146
|
-
className="px-1.5 py-0.5 rounded typography-tiny"
|
|
147
|
-
style={{ backgroundColor: getStatusStyle(status.value).bg, color: getStatusStyle(status.value).text }}
|
|
148
|
-
>
|
|
80
|
+
<Badge size="sm" variant={statusBadgeVariant(status.value)}>
|
|
149
81
|
{status.value.toLowerCase()}
|
|
150
|
-
</
|
|
82
|
+
</Badge>
|
|
151
83
|
)}
|
|
152
84
|
{hasChildren && isCollapsed && (
|
|
153
|
-
<
|
|
154
|
-
className="px-1.5 py-0.5 rounded typography-tiny text-primary-foreground"
|
|
155
|
-
style={{ backgroundColor: color }}
|
|
156
|
-
>
|
|
85
|
+
<Badge size="sm" variant="secondary">
|
|
157
86
|
+{data.childIds.length}
|
|
158
|
-
</
|
|
87
|
+
</Badge>
|
|
159
88
|
)}
|
|
160
89
|
</div>
|
|
161
90
|
</div>
|
|
162
91
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<span
|
|
171
|
-
style={{
|
|
172
|
-
color: highlighted ? theme.node.selectedRing : theme.node.text,
|
|
173
|
-
fontWeight: highlighted ? 600 : undefined,
|
|
174
|
-
}}
|
|
175
|
-
className="truncate text-right tabular-nums"
|
|
176
|
-
title={value}
|
|
177
|
-
>
|
|
178
|
-
{value}
|
|
179
|
-
</span>
|
|
180
|
-
</div>
|
|
181
|
-
))}
|
|
182
|
-
{hiddenPropertiesCount > 0 && (
|
|
183
|
-
<div className="typography-tiny italic" style={{ color: theme.node.textMuted }}>
|
|
184
|
-
+{hiddenPropertiesCount} more
|
|
185
|
-
</div>
|
|
186
|
-
)}
|
|
187
|
-
</div>
|
|
188
|
-
)}
|
|
92
|
+
<AttributeList
|
|
93
|
+
facts={facts}
|
|
94
|
+
previewCount={previewCount}
|
|
95
|
+
defaultExpanded={defaultExpanded}
|
|
96
|
+
searchClassName="px-3"
|
|
97
|
+
bodyClassName="px-3 py-2"
|
|
98
|
+
/>
|
|
189
99
|
</div>
|
|
190
100
|
);
|
|
191
101
|
});
|