@vertesia/ui 0.76.0 → 0.78.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.
Files changed (43) hide show
  1. package/lib/esm/core/components/SidePanel.js +1 -1
  2. package/lib/esm/core/components/SidePanel.js.map +1 -1
  3. package/lib/esm/core/hooks/PortalContainerProvider.js +42 -0
  4. package/lib/esm/core/hooks/PortalContainerProvider.js.map +1 -0
  5. package/lib/esm/core/hooks/index.js +1 -0
  6. package/lib/esm/core/hooks/index.js.map +1 -1
  7. package/lib/esm/features/store/objects/DocumentSearchResults.js +15 -1
  8. package/lib/esm/features/store/objects/DocumentSearchResults.js.map +1 -1
  9. package/lib/esm/features/store/objects/components/DocumentIcon.js +1 -1
  10. package/lib/esm/features/store/objects/components/DocumentIcon.js.map +1 -1
  11. package/lib/esm/features/store/objects/search/DocumentSearchContext.js +6 -3
  12. package/lib/esm/features/store/objects/search/DocumentSearchContext.js.map +1 -1
  13. package/lib/esm/shell/apps/AppProjectSelector.js +17 -6
  14. package/lib/esm/shell/apps/AppProjectSelector.js.map +1 -1
  15. package/lib/esm/shell/apps/index.js +1 -1
  16. package/lib/esm/shell/apps/index.js.map +1 -1
  17. package/lib/tsconfig.tsbuildinfo +1 -1
  18. package/lib/types/core/hooks/PortalContainerProvider.d.ts +7 -0
  19. package/lib/types/core/hooks/PortalContainerProvider.d.ts.map +1 -0
  20. package/lib/types/core/hooks/index.d.ts +1 -0
  21. package/lib/types/core/hooks/index.d.ts.map +1 -1
  22. package/lib/types/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  23. package/lib/types/features/store/objects/components/DocumentIcon.d.ts.map +1 -1
  24. package/lib/types/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
  25. package/lib/types/shell/apps/AppProjectSelector.d.ts +1 -1
  26. package/lib/types/shell/apps/AppProjectSelector.d.ts.map +1 -1
  27. package/lib/types/shell/apps/index.d.ts +1 -1
  28. package/lib/types/shell/apps/index.d.ts.map +1 -1
  29. package/lib/vertesia-ui-core.js +1 -1
  30. package/lib/vertesia-ui-core.js.map +1 -1
  31. package/lib/vertesia-ui-features.js +1 -1
  32. package/lib/vertesia-ui-features.js.map +1 -1
  33. package/lib/vertesia-ui-shell.js +1 -1
  34. package/lib/vertesia-ui-shell.js.map +1 -1
  35. package/package.json +4 -4
  36. package/src/core/components/SidePanel.tsx +2 -2
  37. package/src/core/hooks/PortalContainerProvider.tsx +56 -0
  38. package/src/core/hooks/index.ts +1 -0
  39. package/src/features/store/objects/DocumentSearchResults.tsx +19 -2
  40. package/src/features/store/objects/components/DocumentIcon.tsx +7 -2
  41. package/src/features/store/objects/search/DocumentSearchContext.ts +8 -3
  42. package/src/shell/apps/AppProjectSelector.tsx +19 -8
  43. package/src/shell/apps/index.ts +1 -1
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+
3
+ const PortalContainerContext = React.createContext<HTMLElement | undefined>(undefined);
4
+
5
+ function findOrCreatePortalContainer(root: ShadowRoot | Document, id = "plugin-portal-container") {
6
+ // look only at direct children
7
+ for (const child of Array.from(root.children)) {
8
+ if (child instanceof HTMLElement && child.id === id) {
9
+ return child;
10
+ }
11
+ }
12
+ // not found → create
13
+ const container = document.createElement("div");
14
+ container.id = id;
15
+ root.appendChild(container);
16
+ return container;
17
+ }
18
+
19
+ export function PortalContainerProvider({
20
+ children,
21
+ id = "plugin-portal-container",
22
+ }: {
23
+ children: React.ReactNode;
24
+ id?: string;
25
+ }) {
26
+ const ref = React.useRef<HTMLDivElement>(null);
27
+ const [container, setContainer] = React.useState<HTMLElement | null | undefined>(undefined);
28
+
29
+ React.useEffect(() => {
30
+ if (ref.current) {
31
+ const root = ref.current.getRootNode();
32
+ if (root instanceof ShadowRoot || root instanceof Document) {
33
+ const container = findOrCreatePortalContainer(root, id);
34
+ setContainer(container);
35
+ } else {
36
+ setContainer(null);
37
+ }
38
+ }
39
+ }, [id]);
40
+
41
+ // If container not discovered yet → render hidden marker only
42
+ if (container === undefined) {
43
+ return <div ref={ref} style={{ display: "none" }} />;
44
+ }
45
+
46
+ // Once container is resolved (null or HTMLElement) → provide it
47
+ return (
48
+ <PortalContainerContext.Provider value={container || undefined}>
49
+ {children}
50
+ </PortalContainerContext.Provider>
51
+ );
52
+ }
53
+
54
+ export function usePortalContainer() {
55
+ return React.useContext(PortalContainerContext);
56
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./CompositeState.js"
2
+ export * from "./PortalContainerProvider.js"
2
3
  export * from "./SharedState.js"
3
4
  export * from "./useClickOutside.js"
4
5
  export * from "./useCopyToClipboard.js"
@@ -1,4 +1,4 @@
1
- import { useRef, useState } from "react";
1
+ import { useRef, useState, useEffect } from "react";
2
2
  import { ColumnLayout, ContentObject, ContentObjectItem, ComplexSearchQuery } from '@vertesia/common';
3
3
  import {
4
4
 
@@ -12,6 +12,7 @@ import { useDocumentFilterGroups, useDocumentFilterHandler } from "../../facets/
12
12
  import { VectorSearchWidget } from './components/VectorSearchWidget';
13
13
  import { ContentDispositionButton } from './components/ContentDispositionButton';
14
14
  import { DocumentTable } from './DocumentTable';
15
+ import { ExtendedColumnLayout } from './layout/DocumentTableColumn';
15
16
  import { useDocumentSearch, useWatchDocumentSearchFacets, useWatchDocumentSearchResult } from './search/DocumentSearchContext';
16
17
  import { useDocumentUploadHandler } from './upload/useUploadHandler';
17
18
  import { ContentOverview } from './components/ContentOverview';
@@ -101,6 +102,21 @@ export function DocumentSearchResults({ layout, onUpload, allowFilter = true, al
101
102
  const [filters, setFilters] = useState<BaseFilter[]>([]);
102
103
 
103
104
  const loadMoreRef = useRef<HTMLDivElement>(null);
105
+
106
+ // Trigger initial search when component mounts
107
+ useEffect(() => {
108
+ if (!isReady && objects.length === 0) {
109
+ // Manually set loading state to show spinner during initial load
110
+ search._updateRunningState(true);
111
+ search.search().then(() => {
112
+ setIsReady(true);
113
+ }).catch(err => {
114
+ console.error('Initial search failed:', err);
115
+ search._updateRunningState(false);
116
+ });
117
+ }
118
+ }, []);
119
+
104
120
  useIntersectionObserver(loadMoreRef, () => {
105
121
  if (isReady && objects.length > 0 && objects.length != loaded) {
106
122
  setIsReady(false);
@@ -126,7 +142,8 @@ export function DocumentSearchResults({ layout, onUpload, allowFilter = true, al
126
142
  {
127
143
  name: "Search Score",
128
144
  field: "score",
129
- } satisfies ColumnLayout,
145
+ render: (item) => (item as any).score?.toFixed(4) || "0.0000"
146
+ } satisfies ExtendedColumnLayout,
130
147
  ];
131
148
  setActualLayout(layout);
132
149
  }
@@ -77,8 +77,8 @@ export function DocumentIcon({ selection, document, onSelectionChange, onRowClic
77
77
  )
78
78
  }
79
79
  <Separator className='bg-gray-200 h-[2px]' />
80
- <CardContent className="p-2 flex flex-col gap-1">
81
- <div className="flex flex-col">
80
+ <CardContent className="p-2 flex flex-col">
81
+ <div className="flex flex-col overflow-hidden">
82
82
  <VTooltip
83
83
  placement='top'
84
84
  description={document.properties?.title ?? document.name}>
@@ -94,6 +94,11 @@ export function DocumentIcon({ selection, document, onSelectionChange, onRowClic
94
94
  ) : <p className="text-xs text-muted">{"\u2002"}</p>
95
95
  }
96
96
  </div>
97
+ {document.score && (
98
+ <div className="text-xs text-muted w-full flex justify-end">
99
+ Score: {(document.score).toFixed(4) ?? "-"}
100
+ </div>
101
+ )}
97
102
  </CardContent>
98
103
  </Card>
99
104
  )
@@ -107,9 +107,11 @@ export class DocumentSearch implements SearchInterface {
107
107
  facets: includeFacets ? this.facetSpecs : undefined
108
108
  };
109
109
 
110
- return this.collectionId ?
110
+ const request = this.collectionId ?
111
111
  this.client.collections.searchMembers(this.collectionId, payload)
112
112
  : this.client.objects.search(payload);
113
+
114
+ return request;
113
115
  }
114
116
 
115
117
  _facetsRequest() {
@@ -126,7 +128,7 @@ export class DocumentSearch implements SearchInterface {
126
128
  }
127
129
 
128
130
  _search(loadMore = false, noFacets = false) {
129
- if (this.isRunning) { // avoid searching when a search is pending
131
+ if (this.isRunning && loadMore) { // avoid searching when a search is pending, but allow initial search
130
132
  return Promise.resolve(false);
131
133
  }
132
134
  this.result.value = {
@@ -165,7 +167,10 @@ export class DocumentSearch implements SearchInterface {
165
167
  }
166
168
 
167
169
  search(noFacets = false) {
168
- if (this.isRunning) return Promise.resolve(false);
170
+ // Allow initial search even if isLoading is true (for initial page load)
171
+ if (this.isRunning && this.objects.length > 0) {
172
+ return Promise.resolve(false);
173
+ }
169
174
  return this._search(false, noFacets);
170
175
  }
171
176
 
@@ -1,11 +1,11 @@
1
1
  import { ProjectRef, RequireAtLeastOne } from "@vertesia/common";
2
2
  import { SelectBox, useFetch } from "@vertesia/ui/core";
3
- import { useUserSession } from "@vertesia/ui/session";
3
+ import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY, useUserSession } from "@vertesia/ui/session";
4
4
  import { useState } from "react";
5
5
 
6
6
  interface AppProjectSelectorProps {
7
7
  app: RequireAtLeastOne<{ id?: string, name?: string }, 'id' | 'name'>;
8
- onChange: (value: ProjectRef) => void;
8
+ onChange?: (value: ProjectRef) => void | boolean;
9
9
  placeholder?: string;
10
10
  }
11
11
  export function AppProjectSelector({ app, onChange, placeholder }: AppProjectSelectorProps) {
@@ -14,11 +14,23 @@ export function AppProjectSelector({ app, onChange, placeholder }: AppProjectSel
14
14
  return client.apps.getAppInstallationProjects(app);
15
15
  }, [app.id, app.name])
16
16
 
17
+ const _onChange = (project: ProjectRef) => {
18
+ if (onChange) {
19
+ if (!onChange(project)) {
20
+ // if onChange returns true then the defualt on change is called
21
+ return;
22
+ }
23
+ }
24
+ // default on change
25
+ localStorage.setItem(LastSelectedAccountId_KEY, project.account);
26
+ localStorage.setItem(LastSelectedProjectId_KEY + '-' + project.account, project.id);
27
+ window.location.reload();
28
+ }
29
+
17
30
  if (error) {
18
31
  return <span className='text-red-600'>Error: failed to fetch projects: {error.message}</span>
19
32
  }
20
-
21
- return <SelectProject placeholder={placeholder} initialValue={project?.id} projects={projects || []} onChange={onChange} />
33
+ return <SelectProject placeholder={placeholder} initialValue={project?.id} projects={projects || []} onChange={_onChange} />
22
34
  }
23
35
 
24
36
  interface SelectProjectProps {
@@ -28,17 +40,16 @@ interface SelectProjectProps {
28
40
  placeholder?: string;
29
41
  }
30
42
  function SelectProject({ initialValue, projects, onChange, placeholder = "Select Project" }: Readonly<SelectProjectProps>) {
31
- const [value, setValue] = useState<ProjectRef | undefined>(() => {
32
- return initialValue ? projects.find(p => p.id === initialValue) : undefined
33
- });
43
+ const [value, setValue] = useState<ProjectRef | undefined>();
34
44
  const _onChange = (value: ProjectRef) => {
35
45
  setValue(value)
36
46
  onChange(value)
37
47
  }
48
+ let actualValue = !value && initialValue ? projects.find(p => p.id === initialValue) : value;
38
49
  return (
39
50
  <SelectBox
40
51
  by="id"
41
- value={value}
52
+ value={actualValue}
42
53
  options={projects}
43
54
  optionLabel={(option) => option.name}
44
55
  placeholder={placeholder}
@@ -1,3 +1,3 @@
1
1
  export * from "./AppInstallationProvider";
2
+ export * from "./AppProjectSelector";
2
3
  export * from "./StandaloneApp";
3
- export * from "./AppProjectSelector";