@sinequa/atomic-angular 1.17.0 → 2.0.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 (77) hide show
  1. package/README.md +23 -18
  2. package/components/navbar-tabs/i18n/de.json +4 -2
  3. package/components/navbar-tabs/i18n/en.json +4 -2
  4. package/components/navbar-tabs/i18n/fr.json +5 -3
  5. package/components/preview/i18n/de.json +12 -0
  6. package/components/preview/i18n/en.json +12 -0
  7. package/components/preview/i18n/fr.json +12 -0
  8. package/components/sort-selector/i18n/de.json +1 -0
  9. package/components/sort-selector/i18n/en.json +1 -0
  10. package/components/sort-selector/i18n/fr.json +1 -0
  11. package/components/sponsored-results/i18n/de.json +3 -0
  12. package/components/sponsored-results/i18n/en.json +3 -0
  13. package/components/sponsored-results/i18n/fr.json +3 -0
  14. package/document-workspaces/i18n/de.json +2 -0
  15. package/document-workspaces/i18n/en.json +2 -0
  16. package/document-workspaces/i18n/fr.json +2 -0
  17. package/document-workspaces/i18n/ja.json +2 -0
  18. package/features/{drawer → advanced-search}/i18n/de.json +12 -1
  19. package/features/{drawer → advanced-search}/i18n/en.json +12 -1
  20. package/features/{drawer → advanced-search}/i18n/fr.json +13 -2
  21. package/features/alerts/i18n/de.json +7 -1
  22. package/features/alerts/i18n/en.json +7 -1
  23. package/features/alerts/i18n/fr.json +7 -1
  24. package/features/auth/i18n/de.json +6 -0
  25. package/features/auth/i18n/en.json +6 -0
  26. package/features/auth/i18n/fr.json +6 -1
  27. package/features/collections/i18n/de.json +4 -1
  28. package/features/collections/i18n/en.json +4 -1
  29. package/features/collections/i18n/fr.json +4 -1
  30. package/features/dialogs/i18n/de.json +11 -1
  31. package/features/dialogs/i18n/en.json +11 -1
  32. package/features/dialogs/i18n/fr.json +11 -1
  33. package/features/export/i18n/de.json +5 -1
  34. package/features/export/i18n/en.json +5 -1
  35. package/features/export/i18n/fr.json +5 -1
  36. package/features/feedback/i18n/de.json +2 -1
  37. package/features/feedback/i18n/en.json +2 -1
  38. package/features/feedback/i18n/fr.json +2 -1
  39. package/features/filters/i18n/de.json +10 -3
  40. package/features/filters/i18n/en.json +10 -3
  41. package/features/filters/i18n/fr.json +11 -4
  42. package/features/labels/i18n/de.json +5 -1
  43. package/features/labels/i18n/en.json +5 -1
  44. package/features/labels/i18n/fr.json +5 -1
  45. package/features/list-panel/i18n/de.json +4 -0
  46. package/features/list-panel/i18n/en.json +4 -0
  47. package/features/list-panel/i18n/fr.json +4 -0
  48. package/features/searches/i18n/de.json +2 -1
  49. package/features/searches/i18n/en.json +2 -1
  50. package/features/searches/i18n/fr.json +3 -2
  51. package/features/sheet/i18n/de.json +3 -0
  52. package/features/sheet/i18n/en.json +3 -0
  53. package/features/sheet/i18n/fr.json +3 -0
  54. package/features/user-profile/i18n/de.json +22 -1
  55. package/features/user-profile/i18n/en.json +22 -1
  56. package/features/user-profile/i18n/fr.json +22 -1
  57. package/fesm2022/sinequa-atomic-angular-charts-heatmap.mjs +294 -0
  58. package/fesm2022/sinequa-atomic-angular-charts-heatmap.mjs.map +1 -0
  59. package/fesm2022/sinequa-atomic-angular-charts.mjs +969 -0
  60. package/fesm2022/sinequa-atomic-angular-charts.mjs.map +1 -0
  61. package/fesm2022/sinequa-atomic-angular-document-workspaces.mjs +11197 -7899
  62. package/fesm2022/sinequa-atomic-angular-document-workspaces.mjs.map +1 -1
  63. package/fesm2022/sinequa-atomic-angular-workspace-elements.mjs +85 -152
  64. package/fesm2022/sinequa-atomic-angular-workspace-elements.mjs.map +1 -1
  65. package/fesm2022/sinequa-atomic-angular.mjs +30683 -17302
  66. package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
  67. package/package.json +60 -19
  68. package/styles/calendar.css +88 -0
  69. package/types/sinequa-atomic-angular-charts-heatmap.d.ts +154 -0
  70. package/types/sinequa-atomic-angular-charts.d.ts +462 -0
  71. package/types/sinequa-atomic-angular-document-workspaces.d.ts +2527 -0
  72. package/types/sinequa-atomic-angular-workspace-elements.d.ts +49 -0
  73. package/types/sinequa-atomic-angular.d.ts +19918 -0
  74. package/workspace-elements/styles/lexiq-workspaces.css +7310 -4010
  75. package/document-workspaces/index.d.ts +0 -2159
  76. package/index.d.ts +0 -16119
  77. package/workspace-elements/index.d.ts +0 -37
@@ -0,0 +1,2527 @@
1
+ import { SecurityPrincipal, UpdateContainerInput, UploadJobStatus } from "@sinequa/atomic";
2
+ import * as i0 from "@angular/core";
3
+ import { InjectionToken, PipeTransform, Signal } from "@angular/core";
4
+ import { DetailedError } from "tus-js-client";
5
+ import { DialogResult } from "@sinequa/galactik";
6
+ import { SafeResourceUrl } from "@angular/platform-browser";
7
+ import { FieldTree } from "@angular/forms/signals";
8
+ /** What the current user may do in a workspace. A reader can only look. */
9
+ type WorkspaceRole = "creator" | "owner" | "reader";
10
+ /** Accent a workspace's folder is drawn with — derived from its id, see `workspaceColorFor()`. */
11
+ type WorkspaceColor = "default" | "teal" | "pink" | "blue" | "orange" | "indigo" | "green";
12
+ /** A set of documents, stored server-side as a "container". */
13
+ interface Workspace {
14
+ id: string;
15
+ name: string;
16
+ color: WorkspaceColor;
17
+ description: string;
18
+ tags: string[];
19
+ documentCount: number;
20
+ creatorId: string;
21
+ ownerIds: string[];
22
+ readerIds: string[];
23
+ currentUserRole: WorkspaceRole;
24
+ }
25
+ /** Lifecycle of a document being added: `null` (or absent) once it is indexed. */
26
+ type DocumentStatus = "uploading" | "indexing" | "error";
27
+ type DocumentType = "pdf" | "csv" | "doc";
28
+ interface WorkspaceDocument {
29
+ id: string;
30
+ name: string;
31
+ workspaceId?: string;
32
+ type: DocumentType;
33
+ /**
34
+ * UTC ISO 8601; `undefined` when the server does not know it. The listing only
35
+ * carries the creation date — how it is displayed is the view's business.
36
+ */
37
+ createdAt?: string;
38
+ /**
39
+ * Bytes. Neither the documents listing nor the upload-status endpoint serves a
40
+ * size (swagger `ContainerDocumentViewModel`, checked 2026-09-30): unset for now.
41
+ */
42
+ size?: number;
43
+ /** Not served by the listing either; kept for a "source" column when a backend provides one. */
44
+ source?: string;
45
+ status?: DocumentStatus | null;
46
+ uploadedBy?: string;
47
+ /**
48
+ * Upload progress, 0..100 (100 once the document is fully received) — not
49
+ * indexing progress. From the documents listing, which reads the server's
50
+ * status store: a document shows up there from the moment it starts
51
+ * uploading, not only once indexed.
52
+ */
53
+ percent?: number;
54
+ /** Ties a document back to a job of the upload-status endpoint. */
55
+ jobId?: string;
56
+ }
57
+ /** The two roles a member can be given; `creator` is immutable and never assigned. */
58
+ type AssignableRole = "owner" | "reader";
59
+ /** One person picked in the principal picker — a chip, and one entry of the owners/readers arrays. */
60
+ interface PickedPrincipal {
61
+ /** The principal's `userId` (`domain|id`): what a workspace's owners/readers hold. */
62
+ id: string;
63
+ name: string;
64
+ isGroup: boolean;
65
+ role: AssignableRole;
66
+ }
67
+ /** One row of the access list. */
68
+ interface Member {
69
+ id: string;
70
+ name: string;
71
+ /** Secondary line under the name, when the directory gave one. */
72
+ email?: string;
73
+ isGroup: boolean;
74
+ /** The logged-in user's own row: marked "(you)", and without a role menu. */
75
+ isSelf: boolean;
76
+ role: "creator" | AssignableRole;
77
+ /** False when the id has no cached name and `name` is the raw id. */
78
+ resolved: boolean;
79
+ }
80
+ /**
81
+ * Where a file stands in the tracker.
82
+ *
83
+ * `rejected` is terminal like `done` and `error`: the server will never index
84
+ * the file (a type outside the workspace's conversion plan). Without a state of
85
+ * its own such a file would sit under "Indexing" for ever.
86
+ */
87
+ type UploadStatus = "uploading" | "indexing" | "done" | "error" | "rejected";
88
+ /** A file the tracker follows, from the first byte sent to the end of indexing. */
89
+ interface TrackedUpload {
90
+ /** `<jobId>/<fileName>`: the job and file name the status API reports errors under. Not the server's document id. */
91
+ id: string;
92
+ name: string;
93
+ type: DocumentType;
94
+ workspaceId: string;
95
+ status: UploadStatus;
96
+ /** Upload progress while `uploading`, indexing progress while `indexing`; 0..100. */
97
+ percent: number;
98
+ /** The server's explanation of an `error` (job `errors[].error`) or a `rejected` (`rejected[].reason`). */
99
+ message?: string;
100
+ /** An `error` that sending the file again could fix (a lost connection), as opposed to one the server will keep refusing. */
101
+ retryable: boolean;
102
+ /**
103
+ * Set on a row that does not come from this client's own transfers but from the
104
+ * status API: a job (or one of its failed files) started elsewhere. A job row is
105
+ * the whole job — the API gives no list of its files — so it has no `name`, and
106
+ * nothing here can cancel or retry it.
107
+ */
108
+ remote?: RemoteUpload;
109
+ }
110
+ /** What the tracker knows about a row it only learned from the status API. */
111
+ interface RemoteUpload {
112
+ /** The current user started this job (from another tab, or before a page reload). */
113
+ mine: boolean;
114
+ }
115
+ /** What to tell the tracker when files start. */
116
+ interface TrackFileInput {
117
+ id: string;
118
+ name: string;
119
+ type: DocumentType;
120
+ }
121
+ /**
122
+ * What the tracker's connection banner is currently saying. Published whenever
123
+ * the situation changes, and `null` once everything is healthy again.
124
+ */
125
+ interface ConnectionNotice {
126
+ /**
127
+ * - `retrying` - a transfer is waiting out `retryAtMs` before its next attempt;
128
+ * - `reconnecting` - a session recovery is in flight;
129
+ * - `signed-out` - recovery came back negative; nothing moves until the user
130
+ * signs in again.
131
+ */
132
+ phase: "retrying" | "reconnecting" | "signed-out";
133
+ /** Transfers held up by this. 0 when only the status poll is affected. */
134
+ uploads: number;
135
+ /** 1-based number of the attempt about to be made, and the budget it comes from. */
136
+ attempt: number;
137
+ maxAttempts: number;
138
+ /** Epoch ms of the next attempt - the countdown's target (`retrying` only). */
139
+ retryAtMs?: number;
140
+ /** Epoch ms at which the retry budget runs out (`retrying` only). */
141
+ budgetEndsAtMs?: number;
142
+ /** Epoch ms the connection was first reported lost - drives "lost N ago". */
143
+ sinceMs: number;
144
+ /**
145
+ * Re-runs the recovery, for the banner's Retry button. Carried on the notice
146
+ * because it is the one thing the banner can DO, and it is meaningless without
147
+ * the notice it belongs to. Absent when there is nothing useful to retry.
148
+ */
149
+ retry?(): void;
150
+ }
151
+ /**
152
+ * Most workspaces the list keeps loaded: "load more" stops there, so the list never puts an
153
+ * unbounded number of cards in the DOM. Past it, {@link WorkspacesStore.truncated} is true.
154
+ */
155
+ declare const MAX_LOADED_WORKSPACES: number;
156
+ interface Pagination {
157
+ page: number;
158
+ total: number;
159
+ }
160
+ interface WorkspacesState {
161
+ workspaces: Workspace[];
162
+ /** True once the first page of workspaces has been read from the server. */
163
+ loaded: boolean;
164
+ /** True while a (re)load is in flight. */
165
+ loading: boolean;
166
+ pagination: Pagination;
167
+ documents: Record<string, WorkspaceDocument[]>;
168
+ documentPagination: Record<string, Pagination>;
169
+ /** Ids ticked in the "+"/"@" mention pickers — shared between every picker instance. */
170
+ selectedIds: string[];
171
+ }
172
+ interface CreateWorkspaceInput {
173
+ name: string;
174
+ description?: string;
175
+ tags?: string[];
176
+ /** Optional: they go out on the same POST as the metadata, so sharing at creation costs no extra round trip. */
177
+ owners?: string[];
178
+ readers?: string[];
179
+ }
180
+ interface RemoveWorkspacesResult {
181
+ succeeded: string[];
182
+ failed: {
183
+ id: string;
184
+ error: string;
185
+ }[];
186
+ }
187
+ /**
188
+ * Workspaces and their documents, backed by the container-management API
189
+ * (`@sinequa/atomic`'s `*Container*` functions).
190
+ *
191
+ * Root-scoped on purpose: the workspace list, the mention picker and the
192
+ * save/move picker are separate components that have to see the same cache and
193
+ * the same selection.
194
+ *
195
+ * Every write method resolves or rejects around the matching endpoint and only
196
+ * touches the cache once the server has answered, so a rejected call leaves
197
+ * the state as it was — the call site reports the error.
198
+ *
199
+ * `addDocuments`, `discardDocuments` and `patchDocument` stay purely local. The upload
200
+ * flow no longer uses them: the list is the index, and only the listing writes to it.
201
+ */
202
+ export declare const WorkspacesStore: import("@angular/core").Type<{
203
+ workspaces: import("@angular/core").Signal<Workspace[]>;
204
+ loaded: import("@angular/core").Signal<boolean>;
205
+ loading: import("@angular/core").Signal<boolean>;
206
+ pagination: import("@ngrx/signals").DeepSignal<Pagination>;
207
+ documents: import("@angular/core").Signal<Record<string, WorkspaceDocument[]>>;
208
+ documentPagination: import("@angular/core").Signal<Record<string, Pagination>>;
209
+ selectedIds: import("@angular/core").Signal<string[]>;
210
+ hasMore: import("@angular/core").Signal<boolean>;
211
+ truncated: import("@angular/core").Signal<boolean>;
212
+ selectedCount: import("@angular/core").Signal<number>;
213
+ selectedIdSet: import("@angular/core").Signal<Set<string>>;
214
+ workspace: (id: string) => Workspace | undefined;
215
+ documentsOf: (workspaceId: string) => WorkspaceDocument[];
216
+ hasMoreDocuments: (workspaceId: string) => boolean;
217
+ ensureLoaded: () => Promise<Workspace[]>;
218
+ reload: () => Promise<Workspace[]>;
219
+ loadMore: () => Promise<Workspace[]>;
220
+ fetch: (id: string) => Promise<Workspace>;
221
+ create: ({ name, description, tags, owners, readers }: CreateWorkspaceInput) => Promise<Workspace>;
222
+ update: (id: string, patch: UpdateContainerInput) => Promise<Workspace | null>;
223
+ remove: (ids: string[]) => Promise<RemoveWorkspacesResult>;
224
+ forget: (id: string) => void;
225
+ loadDocuments: (workspaceId: string, signal?: AbortSignal) => Promise<WorkspaceDocument[]>;
226
+ loadMoreDocuments: (workspaceId: string) => Promise<WorkspaceDocument[]>;
227
+ addDocuments: (workspaceId: string, documents: WorkspaceDocument[]) => void;
228
+ discardDocuments: (workspaceId: string, documentIds: string[]) => void;
229
+ patchDocument: (workspaceId: string, documentId: string, patch: Partial<WorkspaceDocument>) => void;
230
+ saveDocuments: (workspaceId: string, documents: Pick<WorkspaceDocument, "name">[]) => Promise<WorkspaceDocument[]>;
231
+ removeDocuments: (workspaceId: string, documentIds: string[]) => Promise<void>;
232
+ moveDocuments: (fromWorkspaceId: string, toWorkspaceId: string, documentIds: string[]) => Promise<void>;
233
+ toggleSelected: (id: string, checked: boolean) => void;
234
+ clearSelection: () => void;
235
+ } & import("@ngrx/signals").StateSource<{
236
+ workspaces: Workspace[];
237
+ loaded: boolean;
238
+ loading: boolean;
239
+ pagination: Pagination;
240
+ documents: Record<string, WorkspaceDocument[]>;
241
+ documentPagination: Record<string, Pagination>;
242
+ selectedIds: string[];
243
+ }>>;
244
+ interface TrackerWorkspace {
245
+ id: string;
246
+ name: string;
247
+ }
248
+ interface UploadTrackerState {
249
+ uploads: TrackedUpload[];
250
+ /** The workspaces being uploaded to, in the order they were first seen. */
251
+ workspaces: TrackerWorkspace[];
252
+ connection: ConnectionNotice | null;
253
+ /** Files dropped from the tracker because their whole workspace finished — they still count as indexed. */
254
+ clearedCount: number;
255
+ /** What the server last said about each job's indexing, 0-100: indexed files / files of the job. */
256
+ jobIndexing: Record<string, number>;
257
+ /**
258
+ * Jobs the user got rid of — closed, cancelled, cleared. The status API keeps listing a
259
+ * job for a while after it ends, so without this a job closed by hand would come
260
+ * straight back as an upload started elsewhere. Survives `reset()`, on purpose.
261
+ */
262
+ ignoredJobs: Record<string, true>;
263
+ }
264
+ /** A job the status API reports that this client did not start — or no longer remembers starting. */
265
+ interface RemoteJobEntry {
266
+ job: UploadJobStatus;
267
+ workspaceId: string;
268
+ workspaceName: string;
269
+ /** The current user started the job (`initiatedBy` is the current user's id). */
270
+ mine: boolean;
271
+ }
272
+ export declare const isFinished: (upload: TrackedUpload) => boolean;
273
+ export declare const isFailed: (upload: TrackedUpload) => boolean;
274
+ interface PhaseProgress {
275
+ uploading: number;
276
+ indexing: number;
277
+ }
278
+ /**
279
+ * How far along a set of uploads is, for each of the two phases, as one 0-100 number.
280
+ *
281
+ * Measured over the files of every job that still has something moving in the phase —
282
+ * and over ALL of that job's files (a failed or refused one aside), not just the ones
283
+ * currently listed in the phase. A file that moved on is a file that finished the phase:
284
+ * counting only the ones left would make the bar drop, or stall, each time one finishes.
285
+ * That is also how the server counts (`indexed files / files of the job`), so the two agree.
286
+ *
287
+ * - uploading: the files still sending count their own percentage, the others are at 100;
288
+ * - indexing: the API has no per-file percentage, only the job's aggregate. What the
289
+ * server last said and what is already known locally (files done / files) are both
290
+ * lower bounds of the truth: the larger one wins, so the bar never falls back.
291
+ */
292
+ declare function phaseProgress(uploads: readonly TrackedUpload[], jobIndexing: Record<string, number>): PhaseProgress;
293
+ /**
294
+ * The state of every upload the tracker follows — pure state, no I/O.
295
+ *
296
+ * `UploadService` writes to it as bytes move and as the server reports; the
297
+ * tracker panel only reads it. Because nothing here talks to the network, a
298
+ * test or a demo page can drive the panel by injecting this store and calling
299
+ * `track()` / `setProgress()` / `setDone()`… directly.
300
+ *
301
+ * Root-scoped: an upload must outlive the page that started it.
302
+ */
303
+ export declare const UploadTrackerStore: import("@angular/core").Type<{
304
+ uploads: import("@angular/core").Signal<TrackedUpload[]>;
305
+ workspaces: import("@angular/core").Signal<TrackerWorkspace[]>;
306
+ connection: import("@ngrx/signals").DeepSignal<ConnectionNotice> | import("@angular/core").Signal<null>;
307
+ clearedCount: import("@angular/core").Signal<number>;
308
+ jobIndexing: import("@angular/core").Signal<Record<string, number>>;
309
+ ignoredJobs: import("@angular/core").Signal<Record<string, true>>;
310
+ active: import("@angular/core").Signal<boolean>;
311
+ failed: import("@angular/core").Signal<TrackedUpload[]>;
312
+ byStatus: import("@angular/core").Signal<Record<UploadStatus, TrackedUpload[]>>;
313
+ allDone: import("@angular/core").Signal<boolean>;
314
+ settled: import("@angular/core").Signal<boolean>;
315
+ authorFailed: import("@angular/core").Signal<boolean>;
316
+ progress: import("@angular/core").Signal<Record<string, PhaseProgress>>;
317
+ indexedCount: import("@angular/core").Signal<number>;
318
+ track: (files: TrackFileInput[], workspaceId: string, workspaceName: string) => void;
319
+ setProgress: (id: string, percent: number) => void;
320
+ setIndexing: (id: string) => void;
321
+ setDone: (id: string) => void;
322
+ setError: (id: string, options?: {
323
+ message?: string;
324
+ retryable?: boolean;
325
+ }) => void;
326
+ restart: (id: string) => void;
327
+ applyJobs: (jobs: UploadJobStatus[]) => boolean;
328
+ applyRemoteJobs: (entries: RemoteJobEntry[]) => boolean;
329
+ setConnection: (connection: ConnectionNotice | null) => void;
330
+ remove: (id: string) => void;
331
+ removeWorkspace: (workspaceId: string) => void;
332
+ reset: () => void;
333
+ } & import("@ngrx/signals").StateSource<{
334
+ uploads: TrackedUpload[];
335
+ workspaces: TrackerWorkspace[];
336
+ connection: ConnectionNotice | null;
337
+ clearedCount: number;
338
+ jobIndexing: Record<string, number>;
339
+ ignoredJobs: Record<string, true>;
340
+ }>>;
341
+ interface UploadTarget {
342
+ id: string;
343
+ name: string;
344
+ }
345
+ interface AddFilesOptions {
346
+ /** The tus endpoint — the `upload-endpoint` of the host. Relative paths resolve against the configured backend. */
347
+ endpoint: string;
348
+ }
349
+ /**
350
+ * Sends files to a workspace and follows them until they are indexed.
351
+ *
352
+ * Two independent things happen after a file is handed over, and both can stall
353
+ * on the same cause (a lost connection, a lapsed session):
354
+ *
355
+ * - the byte transfer, by tus, which retries on its own schedule;
356
+ * - indexing, a server-side job tus knows nothing about, observed by polling
357
+ * the Document Upload status API.
358
+ *
359
+ * One small state machine here owns both, and the tracker's connection banner
360
+ * speaks for both. What the user sees lives in `UploadTrackerStore`; this
361
+ * service only writes to it.
362
+ *
363
+ * It never writes a file into a workspace's list: the list is the index, and a file
364
+ * enters it only once the container listing returns it. What the service does for the
365
+ * list is re-read that listing while something is in progress.
366
+ *
367
+ * Root-scoped: an upload must outlive the view that started it. It registers its
368
+ * window and auth listeners once, and removes them when the application is destroyed.
369
+ */
370
+ export declare class UploadService {
371
+ private readonly tracker;
372
+ private readonly workspaces;
373
+ private readonly currentUser;
374
+ private readonly createUpload;
375
+ private readonly uploads;
376
+ /**
377
+ * Documents this client is still transferring or waiting on. The server's
378
+ * status store can lag a job by a tick or two, so this is what keeps the
379
+ * polling loop alive until it catches up — and emptying it lets it stop.
380
+ */
381
+ private readonly inFlight;
382
+ private readonly poller;
383
+ private focusedWorkspaceId;
384
+ /** Document id -> the retry tus has scheduled for it. Empty means nothing is waiting. */
385
+ private readonly retrying;
386
+ /** A recovery is in flight. Distinct from `signedOut`: one is "asking", the other is the answer. */
387
+ private recovering;
388
+ /** The last recovery said the user is genuinely signed out. Reset the moment anything re-authenticates. */
389
+ private signedOut;
390
+ /** When the trouble started, so the banner can say for how long. 0 while healthy. */
391
+ private lostSinceMs;
392
+ private recovery;
393
+ constructor();
394
+ /**
395
+ * Stages the files as one ingest job and starts sending them. Everything the
396
+ * user picked goes out as ONE batch (one job id).
397
+ *
398
+ * No client-side gatekeeping: no type allow-list, no count or size cap. What
399
+ * can be indexed is the server's call (the workspace's conversion plan); the
400
+ * files it will not take come back in the job's `rejected[]`. The server does
401
+ * cap a job at 1 GiB on the declared total and answers 413 past it.
402
+ *
403
+ * @returns The tracked id of each file, in order.
404
+ */
405
+ addFiles(workspace: UploadTarget, files: File[], options: AddFilesOptions): string[];
406
+ /**
407
+ * Abandons a transfer and forgets it: stops it, and drops the file from the
408
+ * tracker. Works while it is sending
409
+ * and once it has failed; once the bytes are in, indexing is the server's and
410
+ * there is nothing left to cancel.
411
+ *
412
+ * The server is asked to discard what it received (tus termination extension).
413
+ * It may not support that, in which case the transfer is only stopped and the
414
+ * leftover is reclaimed by the server's own retention.
415
+ */
416
+ cancel(id: string): Promise<void>;
417
+ /** Sends a failed transfer again, from where the server left off. Only a transfer the server did not refuse. */
418
+ retry(id: string): void;
419
+ /**
420
+ * Picks up every transfer whose retry budget ran out while the connection was
421
+ * down. Safe to call at any time: a transfer still retrying is left alone.
422
+ * The `File` is held by tus for as long as the tab is open, which is what makes
423
+ * this recovery automatic — an upload does not survive the tab closing.
424
+ */
425
+ resumeStalled(): void;
426
+ /**
427
+ * Tells the service which workspace is open, so the status polling can be
428
+ * scoped to it (the API documents that as the cheaper lookup) and re-read its
429
+ * listing. `null` when none is. Starts the polling: a page reload loses every
430
+ * local tus handle and the listing no longer says what is in progress, so the
431
+ * status API is the only way to know. The loop stops by itself after one tick
432
+ * when nothing is.
433
+ */
434
+ watch(workspaceId: string | null): void;
435
+ startPolling(): void;
436
+ stopPolling(): void;
437
+ private startUpload;
438
+ private markIndexing;
439
+ private markError;
440
+ private pollUploadStatus;
441
+ private runPollTick;
442
+ /**
443
+ * The jobs of the response the panel should show although this client did not send
444
+ * them. The server decides which ones the response holds — those in progress or just
445
+ * ended, on the workspaces the user can write to — so none is filtered by role or date
446
+ * here; only, with a workspace open, those that belong to it are kept. Whether the user
447
+ * started one is read from `initiatedBy`, which is their `userId`, so it holds after a
448
+ * page reload too.
449
+ */
450
+ private remoteJobs;
451
+ /** Re-reads the workspace's listing. @returns Whether it differs from what was shown. */
452
+ private refreshListing;
453
+ /** The job-level status can finish a file this client was still holding the poller for: release it. */
454
+ private settleFinishedUploads;
455
+ /** Transfers that spent their whole budget and are parked in the error state: what "N uploads paused" counts once retrying has stopped. */
456
+ private stalledUploadCount;
457
+ private publishConnection;
458
+ private noteRetryScheduled;
459
+ /** Drops a scheduled retry — the attempt happened, or will never happen. */
460
+ private clearRetryNotice;
461
+ /**
462
+ * Something came back cleanly, so the session demonstrably works — whatever the
463
+ * last recovery concluded. Without this, one `signed-out` verdict would outlive
464
+ * the sign-in that fixed it and make the NEXT 401, on a healthy session, refuse
465
+ * to retry.
466
+ */
467
+ private noteHealthy;
468
+ /**
469
+ * Recovers the session and picks up whatever was waiting on it. Single-flight:
470
+ * every stalled upload and the status poll hit 401 within the same instant, and
471
+ * one probe answers for all of them.
472
+ */
473
+ private recoverSessionAndResume;
474
+ private runRecovery;
475
+ static ɵfac: i0.ɵɵFactoryDeclaration<UploadService, never>;
476
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
477
+ }
478
+ /**
479
+ * Retry schedule for a tus chunk, in ms. tus caps the number of attempts at this
480
+ * array's LENGTH, so surviving an outage is a matter of how many entries there
481
+ * are, not just how long they are. The total (~5 min) is deliberately sized on
482
+ * the server's own retention: its cleanup reclaims an incomplete upload idle for
483
+ * 5 minutes and then answers 410 Gone, so retrying past that cannot succeed — at
484
+ * which point the upload is recovered by the `online` handler instead.
485
+ */
486
+ declare const UPLOAD_RETRY_DELAYS: number[];
487
+ /**
488
+ * Statuses the ingest endpoint uses to say "this upload will never be accepted":
489
+ * retrying them only burns requests. Everything else — a network error (status
490
+ * 0), a 5xx, a 429 — is transient and worth another attempt.
491
+ *
492
+ * 401 is deliberately NOT in here even though it is a 4xx. An upload that
493
+ * outlives a connection drop comes back holding a CSRF token the server has
494
+ * since forgotten, so every attempt 401s and the whole retry budget burns
495
+ * without a byte moving — which is exactly the failure this list would make
496
+ * permanent. It is handled on its own by the service: recover the session, then
497
+ * let the same retry carry the fresh token.
498
+ */
499
+ declare const FATAL_UPLOAD_STATUSES: ReadonlySet<number>;
500
+ /** What an upload tells whoever drives it. */
501
+ interface UploadEvents {
502
+ /**
503
+ * Whether tus should try again after a failed attempt. `status` is the HTTP
504
+ * status of the failure, 0 for a network error; `attempt` is 0-based and is
505
+ * the index into {@link UPLOAD_RETRY_DELAYS} of the delay tus waited before it.
506
+ */
507
+ shouldRetry(status: number, attempt: number): boolean;
508
+ /** Any response came back — `status` tells whether the connection is working again. */
509
+ responded(status: number): void;
510
+ /** Resolves once the request may go out: a session recovery in flight has to finish first, it is what the retry is for. */
511
+ beforeRequest(): Promise<void>;
512
+ progress(bytesUploaded: number, bytesTotal: number | null): void;
513
+ /** The server holds every byte. Indexing is a separate server-side job with no tus equivalent. */
514
+ success(): void;
515
+ /** The retry budget is spent, or the server refused for good. */
516
+ failed(error: Error): void;
517
+ }
518
+ interface UploadOptions {
519
+ /** Absolute: tus must not resolve a relative endpoint against the host page instead of the configured backend. */
520
+ endpoint: string;
521
+ /** The ingest endpoint validates these keys and answers 400 if one is missing. */
522
+ metadata: Record<string, string>;
523
+ events: UploadEvents;
524
+ }
525
+ /** An upload in flight, whatever moves its bytes. */
526
+ interface UploadHandle {
527
+ /** Starts it — resuming a previous attempt at the same file when tus recognises it (same name, size, type, date). */
528
+ begin(): void;
529
+ /** Sends it again after a failure; tus re-reads the server's offset first, so only the missing bytes go out. */
530
+ resume(): void;
531
+ /**
532
+ * Stops it. With `terminate`, the server is asked to drop what it received
533
+ * (tus termination extension) — which it may not support, in which case this rejects.
534
+ */
535
+ abort(terminate: boolean): Promise<void>;
536
+ }
537
+ type UploadFactory = (file: File, options: UploadOptions) => UploadHandle;
538
+ /** The HTTP status behind a tus error: 0 when there was no response (a network error). */
539
+ declare function tusErrorStatus(error: DetailedError): number;
540
+ /** Sends `file` with tus. */
541
+ declare const createTusUpload: UploadFactory;
542
+ /** Overridable so the service can be driven without a network. */
543
+ export declare const UPLOAD_FACTORY: InjectionToken<UploadFactory>;
544
+ interface CurrentUser {
545
+ /** The principal's `userId` (`domain|id`) — the id space a workspace's owners/readers use. */
546
+ id: string;
547
+ name: string;
548
+ }
549
+ /**
550
+ * The logged-in user's identity — the one thing @sinequa/atomic has no
551
+ * synchronous notion of. Resolved once, on first injection, through
552
+ * `fetchPrincipal()`; `user()` is `undefined` until it settles (and stays so
553
+ * if the request fails).
554
+ *
555
+ * Callers render off `user()` straight away and treat "unknown" as a valid
556
+ * fallback (a raw-id badge, the generic "You" label) rather than an error.
557
+ * Those that must not act on "unknown" as if it were an answer — the
558
+ * "you can't remove yourself" rule needs to know which row is yours —
559
+ * await {@link whenReady}.
560
+ */
561
+ export declare class CurrentUserService {
562
+ private readonly _user;
563
+ private readonly ready;
564
+ readonly user: import("@angular/core").Signal<CurrentUser | undefined>;
565
+ constructor();
566
+ /** The same identity as `user()`, but awaitable. Resolves `undefined` if the principal request failed. */
567
+ whenReady(): Promise<CurrentUser | undefined>;
568
+ static ɵfac: i0.ɵɵFactoryDeclaration<CurrentUserService, never>;
569
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
570
+ }
571
+ interface PrincipalSummary {
572
+ name: string;
573
+ isGroup: boolean;
574
+ /** Users only, and only when the directory has one — the access list's secondary line. */
575
+ email?: string;
576
+ }
577
+ /** A principal id turned into something renderable. */
578
+ interface ResolvedPrincipal {
579
+ /** Display label — the cached name, or the raw id when it isn't cached. Never decorated: see `isSelf`. */
580
+ name: string;
581
+ /** Secondary line under the name, when the directory gave one. */
582
+ email?: string;
583
+ isGroup: boolean;
584
+ /** False when the id has no cached name and `name` is the raw id. */
585
+ resolved: boolean;
586
+ /** True when this id is the logged-in user's. */
587
+ isSelf: boolean;
588
+ }
589
+ /**
590
+ * The label to show for a principal: a user's full name where it has one,
591
+ * otherwise the account name. Groups only ever have `name`.
592
+ */
593
+ export declare function principalDisplayName(principal: SecurityPrincipal): string;
594
+ /**
595
+ * Names for the principal ids a workspace's owners/readers hold, which are
596
+ * opaque `domain|id` strings the API never resolves for us.
597
+ *
598
+ * Persisted in `sessionStorage` per the product decision — it survives
599
+ * navigation within the tab and is cleared when the tab closes — so an access
600
+ * list doesn't flash raw ids on every open.
601
+ *
602
+ * `resolve()` is synchronous and reads signals, so a template or `computed`
603
+ * that calls it re-renders by itself once {@link ensureResolved} fills a gap.
604
+ * An id that is not cached shows up as the raw id, which is also what an id
605
+ * the server can't resolve at all (a deleted account) keeps showing — still
606
+ * enough to keep it in the owners/readers arrays on the next save rather than
607
+ * silently revoking it.
608
+ */
609
+ export declare class PrincipalDirectory {
610
+ private readonly currentUser;
611
+ private readonly cache;
612
+ /**
613
+ * Resolves a principal id for display, identically wherever an owners/readers
614
+ * id is rendered — the access list and the add/edit dialog's "Share with" chips.
615
+ *
616
+ * "(you)" is deliberately NOT folded into the name: it would end up in the
617
+ * avatar's initials and in every message that interpolates a name. Views
618
+ * render it as its own element off `isSelf`.
619
+ */
620
+ resolve(id: string): ResolvedPrincipal;
621
+ /** Records a name learned elsewhere (e.g. a person just picked in the principal picker). */
622
+ remember(id: string, summary: PrincipalSummary): void;
623
+ /**
624
+ * Searches the directory by free text and remembers every name it learns, so a
625
+ * person just picked already has one when the access list shows them.
626
+ *
627
+ * @param query - What the user typed.
628
+ * @param signal - Cancels the request when a newer keystroke supersedes it.
629
+ * @returns Users and groups alike; the caller decides which it can use.
630
+ */
631
+ search(query: string, signal?: AbortSignal): Promise<SecurityPrincipal[]>;
632
+ /**
633
+ * Fetches and caches the names for any of `ids` not already known.
634
+ *
635
+ * @param ids - Principal identities, typically a workspace's owners/readers.
636
+ *
637
+ * @returns Resolves once the cache has been updated. A failed lookup resolves
638
+ * rather than rejecting: the UI already renders unresolved ids, and a name
639
+ * is not worth failing an access dialog over.
640
+ */
641
+ ensureResolved(ids: string[]): Promise<void>;
642
+ static ɵfac: i0.ɵɵFactoryDeclaration<PrincipalDirectory, never>;
643
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
644
+ }
645
+ /**
646
+ * Turns an element into a place to drop files on, or to click to pick some.
647
+ *
648
+ * ```html
649
+ * <div dropzone (filesSelected)="stage($event)">Drop your files here</div>
650
+ * ```
651
+ *
652
+ * Both ways end in the same `filesSelected` output. While files are dragged over
653
+ * it the host gets a `data-dragover` attribute to style. It is a button for
654
+ * assistive technology and the keyboard (Enter or Space opens the file picker).
655
+ *
656
+ * It does no gatekeeping — no type, count or size check: whatever is dropped
657
+ * or picked is emitted, and what can be handled is the server's call.
658
+ */
659
+ export declare class Dropzone {
660
+ /** Files dropped or picked, in the order the browser gave them. */
661
+ readonly filesSelected: import("@angular/core").OutputEmitterRef<File[]>;
662
+ readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
663
+ protected readonly dragging: import("@angular/core").WritableSignal<boolean>;
664
+ private depth;
665
+ protected browse(): void;
666
+ protected onKey(event: Event): void;
667
+ protected onDragEnter(event: DragEvent): void;
668
+ protected onDragOver(event: DragEvent): void;
669
+ protected onDragLeave(event: DragEvent): void;
670
+ protected onDrop(event: DragEvent): void;
671
+ /** A text or a link being dragged around the page is none of our business. */
672
+ private carriesFiles;
673
+ private emit;
674
+ static ɵfac: i0.ɵɵFactoryDeclaration<Dropzone, never>;
675
+ static ɵdir: i0.ɵɵDirectiveDeclaration<Dropzone, "[dropzone]", never, {
676
+ "disabled": {
677
+ "alias": "dropzoneDisabled";
678
+ "required": false;
679
+ "isSignal": true;
680
+ };
681
+ }, {
682
+ "filesSelected": "filesSelected";
683
+ }, never, never, true, never>;
684
+ }
685
+ /**
686
+ * Turns an element into the drag handle of a resizable panel.
687
+ *
688
+ * The panel's width is two-way bound through the directive's own name:
689
+ *
690
+ * ```html
691
+ * <aside [style.width.px]="width()">
692
+ * <div [(resizeHandle)]="width" resizeMin="280" [resizeMax]="maxWidth()"></div>
693
+ * </aside>
694
+ * ```
695
+ *
696
+ * It follows the WAI-ARIA separator pattern: the handle is focusable and the
697
+ * arrow keys resize it, so the panel stays usable without a pointer. The pointer
698
+ * is captured on `pointerdown`, so a drag keeps going when the cursor leaves the
699
+ * handle or crosses an iframe (which would otherwise swallow the events).
700
+ */
701
+ export declare class ResizeHandle {
702
+ /** The panel's width in px. */
703
+ readonly width: import("@angular/core").ModelSignal<number>;
704
+ readonly resizeMin: import("@angular/core").InputSignalWithTransform<number, any>;
705
+ /** Upper bound in px; unbounded when absent. */
706
+ readonly resizeMax: import("@angular/core").InputSignal<number | undefined>;
707
+ /** px moved per arrow key press. */
708
+ readonly resizeStep: import("@angular/core").InputSignalWithTransform<number, any>;
709
+ /**
710
+ * Which edge of the panel the handle sits on — and therefore which way a drag
711
+ * widens it: dragging a `left` handle to the left widens the panel.
712
+ */
713
+ readonly resizeEdge: import("@angular/core").InputSignal<"left" | "right">;
714
+ /** Whether the handle is being dragged. */
715
+ readonly resizing: import("@angular/core").WritableSignal<boolean>;
716
+ private readonly direction;
717
+ private startX;
718
+ private startWidth;
719
+ protected onPointerDown(event: PointerEvent): void;
720
+ protected onPointerMove(event: PointerEvent): void;
721
+ protected onPointerEnd(event: PointerEvent): void;
722
+ protected onKeydown(event: KeyboardEvent): void;
723
+ private clamp;
724
+ static ɵfac: i0.ɵɵFactoryDeclaration<ResizeHandle, never>;
725
+ static ɵdir: i0.ɵɵDirectiveDeclaration<ResizeHandle, "[resizeHandle]", ["resizeHandle"], {
726
+ "width": {
727
+ "alias": "resizeHandle";
728
+ "required": true;
729
+ "isSignal": true;
730
+ };
731
+ "resizeMin": {
732
+ "alias": "resizeMin";
733
+ "required": false;
734
+ "isSignal": true;
735
+ };
736
+ "resizeMax": {
737
+ "alias": "resizeMax";
738
+ "required": false;
739
+ "isSignal": true;
740
+ };
741
+ "resizeStep": {
742
+ "alias": "resizeStep";
743
+ "required": false;
744
+ "isSignal": true;
745
+ };
746
+ "resizeEdge": {
747
+ "alias": "resizeEdge";
748
+ "required": false;
749
+ "isSignal": true;
750
+ };
751
+ }, {
752
+ "width": "resizeHandleChange";
753
+ }, never, never, true, never>;
754
+ }
755
+ /**
756
+ * Hides an element from anyone who cannot change the workspace — a reader, or
757
+ * whoever the role is not known for yet. One rule for every edit / share /
758
+ * delete / upload control, instead of the check being repeated on each.
759
+ *
760
+ * ```html
761
+ * <button [writerOnly]="workspace().currentUserRole" (click)="edit()">Edit</button>
762
+ * ```
763
+ *
764
+ * It sets `hidden`, which also drops the element from the accessibility tree
765
+ * and the tab order; a layout class that sets `display` must not override it.
766
+ */
767
+ export declare class WriterOnly {
768
+ readonly writerOnly: import("@angular/core").InputSignal<WorkspaceRole | null | undefined>;
769
+ protected readonly writable: import("@angular/core").Signal<boolean>;
770
+ static ɵfac: i0.ɵɵFactoryDeclaration<WriterOnly, never>;
771
+ static ɵdir: i0.ɵɵDirectiveDeclaration<WriterOnly, "[writerOnly]", never, {
772
+ "writerOnly": {
773
+ "alias": "writerOnly";
774
+ "required": true;
775
+ "isSignal": true;
776
+ };
777
+ }, {}, never, never, true, never>;
778
+ }
779
+ /** A slice of a text, flagged when it is what a search matched. */
780
+ interface TextChunk {
781
+ text: string;
782
+ match: boolean;
783
+ }
784
+ /** Lower-cases and strips accents, so "Resume" finds "Résumé". */
785
+ declare function fold(text: string): string;
786
+ /**
787
+ * Folds `text` while remembering, for each folded code unit, which slice of the
788
+ * original it came from — folding can change the length (a combining mark
789
+ * vanishes, `İ` becomes two units), so a match position in the folded text is
790
+ * not a position in the original.
791
+ */
792
+ declare function foldWithOffsets(text: string): {
793
+ folded: string;
794
+ starts: number[];
795
+ ends: number[];
796
+ };
797
+ /**
798
+ * Whether `text` contains `query`, ignoring case and accents. An empty query
799
+ * matches everything.
800
+ */
801
+ declare function matchesText(text: string, query: string): boolean;
802
+ /**
803
+ * Splits `text` around every occurrence of `query` (case- and accent-insensitive),
804
+ * keeping the original characters — the accents a user typed or stored are not
805
+ * lost to the matching.
806
+ *
807
+ * It returns data rather than markup on purpose: the text is user-supplied
808
+ * (a workspace or document name) and the view renders the chunks through Angular's
809
+ * own escaping instead of interpolating them into HTML.
810
+ */
811
+ declare function splitByMatch(text: string, query: string): TextChunk[];
812
+ /**
813
+ * Splits a text around what a search matched, ignoring case and accents.
814
+ *
815
+ * Unlike `highlightWord`, it keeps the original characters (that pipe returns
816
+ * chunks of the accent-stripped text, which would display "Donnees" for
817
+ * "Données") and is meant to be rendered, never bound to `innerHTML`:
818
+ *
819
+ * ```html
820
+ * @for (chunk of workspace.name | highlightMatch: query(); track $index) {
821
+ * @if (chunk.match) { <mark>{{ chunk.text }}</mark> } @else { {{ chunk.text }} }
822
+ * }
823
+ * ```
824
+ */
825
+ export declare class HighlightMatchPipe implements PipeTransform {
826
+ transform(text: string | null | undefined, query: string | null | undefined): TextChunk[];
827
+ static ɵfac: i0.ɵɵFactoryDeclaration<HighlightMatchPipe, never>;
828
+ static ɵpipe: i0.ɵɵPipeDeclaration<HighlightMatchPipe, "highlightMatch", true>;
829
+ }
830
+ /**
831
+ * Text to print as is, or a key of the `documentWorkspaces` scope (with its
832
+ * parameters) to translate. Lets a class decide *what* to say and leave *saying
833
+ * it* to the template, which has the translation pipe.
834
+ */
835
+ type Message = {
836
+ text: string;
837
+ } | {
838
+ key: string;
839
+ params?: Record<string, unknown>;
840
+ };
841
+ /** A message from the `tracker` namespace. */
842
+ declare function trackerMessage(key: string, params?: Record<string, unknown>): Message;
843
+ /** Shortens a file name to `max` characters, keeping its extension: `long-na….pdf`. */
844
+ declare function truncateName(name: string, max: number): string;
845
+ /**
846
+ * What to call a tracked row: the file's name — or, for a job started elsewhere whose files
847
+ * the status API does not list, whose job it is.
848
+ */
849
+ declare function uploadLabel(upload: Pick<TrackedUpload, "name" | "remote">, max: number): Message;
850
+ /** `m:ss` for a duration in ms, clamped at 0 so a late tick cannot go negative. */
851
+ declare function formatClock(ms: number): string;
852
+ interface DocumentLabels {
853
+ /** "1.2 MB", or `null` when the size is not known — the API does not serve one, only a file uploaded in this session has it. */
854
+ size: Signal<string | null>;
855
+ /** "Added 3 hours ago", or `null` when the creation date is not known. */
856
+ added: Signal<Message | null>;
857
+ }
858
+ /**
859
+ * The size and date labels a document card and a document row both show,
860
+ * following the active language.
861
+ *
862
+ * Must be called from an injection context (a component field initializer).
863
+ */
864
+ export declare function injectDocumentLabels(document: Signal<WorkspaceDocument>): DocumentLabels;
865
+ /**
866
+ * Whether the browser reports a network connection, kept up to date from the
867
+ * window's `online` / `offline` events. `true` where there is no `navigator`
868
+ * (server-side rendering).
869
+ *
870
+ * `navigator.onLine` only knows about the network interface, not whether the
871
+ * server is reachable: treat `false` as certain and `true` as likely.
872
+ *
873
+ * Must be called from an injection context; the listeners are removed when it is destroyed.
874
+ */
875
+ export declare function injectOnlineStatus(): Signal<boolean>;
876
+ interface SelectionOptions<T> {
877
+ /** What can currently be selected — pass the list as it is displayed (filtered), not the full one. */
878
+ items: Signal<readonly T[]>;
879
+ /** A stable id per item. */
880
+ key: (item: T) => string;
881
+ /**
882
+ * Whether an item may be ticked at all — a workspace shared read-only cannot be
883
+ * deleted, so it should not count towards "3 selected" nor be ticked by "select all".
884
+ * Every item is selectable when absent. Read reactively: it may depend on signals.
885
+ */
886
+ selectable?: (item: T) => boolean;
887
+ }
888
+ interface Selection<T> {
889
+ /** Whether the selection mode (checkboxes, selection bar) is on. */
890
+ readonly active: Signal<boolean>;
891
+ /** The displayed items that can be ticked — empty when there is nothing to offer the selection mode for. */
892
+ readonly selectableItems: Signal<T[]>;
893
+ /** The displayed, selectable items that are ticked. An item filtered out of `items` is not counted. */
894
+ readonly selectedItems: Signal<T[]>;
895
+ readonly count: Signal<number>;
896
+ /** Every selectable displayed item is ticked (never true when there is none). */
897
+ readonly allSelected: Signal<boolean>;
898
+ /** Some, but not all, are ticked — the "indeterminate" state of a select-all checkbox. */
899
+ readonly partiallySelected: Signal<boolean>;
900
+ /** Whether the item may be ticked. Reactive when read from a template or a `computed`. */
901
+ canSelect(item: T): boolean;
902
+ /** Reactive when read from a template or a `computed`. An item that cannot be selected is never selected. */
903
+ isSelected(item: T): boolean;
904
+ /** Does nothing for an item that cannot be selected. */
905
+ toggle(item: T, selected?: boolean): void;
906
+ /** Ticks every selectable displayed item, or unticks them all when they already are. */
907
+ toggleAll(): void;
908
+ /** Turns the selection mode on or off; leaving it clears the selection. */
909
+ toggleMode(): void;
910
+ start(): void;
911
+ /** Leaves the selection mode and clears the selection. */
912
+ exit(): void;
913
+ clear(): void;
914
+ }
915
+ /**
916
+ * Multi-selection over a displayed list — the selection mode, the ticked
917
+ * items, and the state of a select-all checkbox — shared by the workspace list
918
+ * and the document list.
919
+ *
920
+ * Ticks are kept by key, so they survive the list being filtered or switched
921
+ * between grid and list views: a tick on an item that is filtered out stays
922
+ * until it comes back, but only displayed items are counted and returned.
923
+ *
924
+ * Must be called from an injection context (a component field initializer).
925
+ */
926
+ export declare function injectSelection<T>(options: SelectionOptions<T>): Selection<T>;
927
+ declare const SEARCH_HIGHLIGHT = "workspace-search";
928
+ declare const ACTIVE_HIGHLIGHT = "workspace-search-active";
929
+ interface TextSearchOptions {
930
+ /** The node whose text is searched — the body of a same-origin iframe, say. `null` when there is nothing to search. */
931
+ root: Signal<Node | null>;
932
+ query: Signal<string>;
933
+ /** How long the query must stay unchanged before the text is searched, in ms. */
934
+ debounce?: number;
935
+ }
936
+ interface TextSearch {
937
+ /** How many occurrences there are. */
938
+ readonly matches: Signal<number>;
939
+ /** The occurrence in focus (0-based), or -1 when there is none. */
940
+ readonly activeIndex: Signal<number>;
941
+ /** Whether the browser can paint the matches (the CSS Custom Highlight API); when it cannot, they are still counted and scrolled to. */
942
+ readonly highlighting: Signal<boolean>;
943
+ /** Moves to the next occurrence, wrapping round. */
944
+ next(): void;
945
+ previous(): void;
946
+ }
947
+ /**
948
+ * Find-in-text over a document you do not render through Angular, painted with the
949
+ * CSS Custom Highlight API: the text is never touched, the matches are ranges the
950
+ * browser draws.
951
+ *
952
+ * It follows the query (debounced) and the root: a new root is searched again, and
953
+ * whatever was painted on the old one is taken back. Without the API (older
954
+ * browsers) the occurrences are still counted and scrolled to.
955
+ *
956
+ * Must be called from an injection context (a component field initializer).
957
+ */
958
+ export declare function injectTextSearch({ root, query, debounce }: TextSearchOptions): TextSearch;
959
+ /** `editor`: the workspaces you can change (creator or owner); `reader`: those shared with you read-only. */
960
+ type WorkspaceTab = "all" | "editor" | "reader";
961
+ interface WorkspaceTabCounts {
962
+ all: number;
963
+ editor: number;
964
+ reader: number;
965
+ }
966
+ /**
967
+ * The workspace list's tab and search box, and the list they produce.
968
+ *
969
+ * The tab badges count the whole list: typing in the search box narrows what is
970
+ * shown, not the numbers next to the tabs.
971
+ *
972
+ * Must be called from an injection context (a component field initializer).
973
+ */
974
+ export declare function injectWorkspaceFilter(workspaces: Signal<readonly Workspace[]>): {
975
+ tab: import("@angular/core").WritableSignal<WorkspaceTab>;
976
+ query: import("@angular/core").WritableSignal<string>;
977
+ counts: Signal<WorkspaceTabCounts>;
978
+ filtered: Signal<Workspace[]>;
979
+ clearQuery: () => void;
980
+ };
981
+ /**
982
+ * A text with what a search matched marked, ignoring case and accents.
983
+ *
984
+ * Rendered chunk by chunk through Angular's own escaping: the text is a user's
985
+ * workspace or document name, and interpolating it into markup would execute
986
+ * anything HTML-ish it contains. The template is on one line on purpose, so no
987
+ * whitespace sneaks in between the chunks.
988
+ */
989
+ export declare class HighlightedText {
990
+ readonly text: import("@angular/core").InputSignal<string>;
991
+ readonly query: import("@angular/core").InputSignal<string>;
992
+ protected readonly chunks: import("@angular/core").Signal<TextChunk[]>;
993
+ static ɵfac: i0.ɵɵFactoryDeclaration<HighlightedText, never>;
994
+ static ɵcmp: i0.ɵɵComponentDeclaration<HighlightedText, "highlighted-text", never, {
995
+ "text": {
996
+ "alias": "text";
997
+ "required": true;
998
+ "isSignal": true;
999
+ };
1000
+ "query": {
1001
+ "alias": "query";
1002
+ "required": false;
1003
+ "isSignal": true;
1004
+ };
1005
+ }, {}, never, never, true, never>;
1006
+ }
1007
+ /** What the workspace is to you: yours ("Owner", for a creator or owner) or shared with you ("Shared"). */
1008
+ export declare class WorkspaceRoleTag {
1009
+ readonly role: import("@angular/core").InputSignal<WorkspaceRole>;
1010
+ protected readonly owned: import("@angular/core").Signal<boolean>;
1011
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceRoleTag, never>;
1012
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceRoleTag, "workspace-role-tag", never, {
1013
+ "role": {
1014
+ "alias": "role";
1015
+ "required": true;
1016
+ "isSignal": true;
1017
+ };
1018
+ }, {}, never, never, true, never>;
1019
+ }
1020
+ /**
1021
+ * Either a translation key (under the `documentWorkspaces` scope) or text to
1022
+ * show verbatim. The split keeps this module free of any translation engine:
1023
+ * the view passes `key` to the `transloco` pipe and prints `text` as is.
1024
+ */
1025
+ type WorkspaceErrorMessage = {
1026
+ key: string;
1027
+ } | {
1028
+ text: string;
1029
+ };
1030
+ /**
1031
+ * Maps a caught error to the message shown in an inline error banner.
1032
+ *
1033
+ * Only the statuses we recognise get a catalog entry. The default branch
1034
+ * deliberately passes the server's own prose through untranslated (a 409
1035
+ * `container_locked` has no dedicated error class and its message is already
1036
+ * human-readable): localizing the rest would need the API to return stable
1037
+ * error codes instead of English sentences.
1038
+ *
1039
+ * @param err - The caught value, of any type.
1040
+ */
1041
+ declare function workspaceErrorMessage(err: unknown): WorkspaceErrorMessage;
1042
+ interface ConfirmDialogConfig {
1043
+ /** Already translated. */
1044
+ title: string;
1045
+ /** Already translated, with the subject interpolated. */
1046
+ description?: string;
1047
+ /** A substring of `description` to emphasise — typically the name of what is about to be deleted. */
1048
+ emphasis?: string;
1049
+ /** Already translated. Defaults to "Delete" when `destructive`, "Save" otherwise. */
1050
+ confirmLabel?: string;
1051
+ /** Already translated; shown on the confirm button while `action` runs. Defaults to "Deleting…" when `destructive`. */
1052
+ busyLabel?: string;
1053
+ /** Already translated. Defaults to "Cancel". */
1054
+ cancelLabel?: string;
1055
+ /** Styles the confirm button as a destructive action. */
1056
+ destructive?: boolean;
1057
+ /**
1058
+ * What confirming does. While it runs the dialog cannot be dismissed (Cancel,
1059
+ * Escape), so a request cannot be abandoned half-way through; if it rejects,
1060
+ * the dialog stays open with the error so it can be retried. Without it,
1061
+ * confirming just closes the dialog with `dialog-confirm`.
1062
+ */
1063
+ action?: () => Promise<unknown>;
1064
+ }
1065
+ /** Text to print as is, or a key of the `documentWorkspaces` scope to translate. */
1066
+ type Label = {
1067
+ key: string;
1068
+ } | {
1069
+ text: string;
1070
+ };
1071
+ /**
1072
+ * The one confirmation dialog — "delete these workspaces?", "delete this
1073
+ * document?" — instead of a modal per use.
1074
+ *
1075
+ * ```ts
1076
+ * const result = await ConfirmAction.call({
1077
+ * title, description, emphasis: name, destructive: true,
1078
+ * action: () => store.remove([id])
1079
+ * });
1080
+ * if (result === "dialog-confirm") …
1081
+ * ```
1082
+ *
1083
+ * Resolves `dialog-confirm` once the action succeeded, `dialog-cancel` otherwise.
1084
+ */
1085
+ export declare class ConfirmDialog {
1086
+ protected readonly call: import("@sinequa/galactik").CallRef<ConfirmDialogConfig, DialogResult>;
1087
+ private readonly dialog;
1088
+ protected readonly config: import("@angular/core").Signal<ConfirmDialogConfig>;
1089
+ protected readonly busy: import("@angular/core").WritableSignal<boolean>;
1090
+ protected readonly error: import("@angular/core").WritableSignal<WorkspaceErrorMessage | null>;
1091
+ protected readonly descriptionChunks: import("@angular/core").Signal<TextChunk[]>;
1092
+ protected readonly cancelLabel: import("@angular/core").Signal<Label>;
1093
+ protected readonly confirmLabel: import("@angular/core").Signal<Label>;
1094
+ constructor();
1095
+ protected confirm(): Promise<void>;
1096
+ /** Escape on a native <dialog> closes it: refuse while the action is running. */
1097
+ protected onNativeCancel(event: Event): void;
1098
+ static ɵfac: i0.ɵɵFactoryDeclaration<ConfirmDialog, never>;
1099
+ static ɵcmp: i0.ɵɵComponentDeclaration<ConfirmDialog, "workspace-confirm-dialog", never, {}, {}, never, never, true, never>;
1100
+ }
1101
+ /** Imperative entry point: `await ConfirmAction.call({ title, destructive: true, action })`. */
1102
+ export declare const ConfirmAction: import("@sinequa/galactik").Callable<ConfirmDialogConfig, DialogResult>;
1103
+ /**
1104
+ * The badge of a document that is not finished: "1/2 Uploading", "2/2 Indexing"
1105
+ * (with animated dots while it moves) or "Upload failed". Renders nothing for a
1106
+ * document that is indexed, or whose state is not known: the card shows its date.
1107
+ */
1108
+ export declare class DocumentStatusBadge {
1109
+ /** `null` or absent once the document is indexed. */
1110
+ readonly status: import("@angular/core").InputSignal<DocumentStatus | null | undefined>;
1111
+ protected readonly message: import("@angular/core").Signal<Message | null>;
1112
+ static ɵfac: i0.ɵɵFactoryDeclaration<DocumentStatusBadge, never>;
1113
+ static ɵcmp: i0.ɵɵComponentDeclaration<DocumentStatusBadge, "document-status-badge", never, {
1114
+ "status": {
1115
+ "alias": "status";
1116
+ "required": false;
1117
+ "isSignal": true;
1118
+ };
1119
+ }, {}, never, never, true, never>;
1120
+ }
1121
+ /** The icon of a document, on a tile tinted by the family `getDocumentType()` puts its file name in. */
1122
+ export declare class DocumentTypeIcon {
1123
+ readonly type: import("@angular/core").InputSignal<DocumentType>;
1124
+ /** `lg` (36px) on a card, `md` (32px) in a row. */
1125
+ readonly size: import("@angular/core").InputSignal<"md" | "lg">;
1126
+ protected readonly classes: import("@angular/core").Signal<string>;
1127
+ static ɵfac: i0.ɵɵFactoryDeclaration<DocumentTypeIcon, never>;
1128
+ static ɵcmp: i0.ɵɵComponentDeclaration<DocumentTypeIcon, "document-type-icon", never, {
1129
+ "type": {
1130
+ "alias": "type";
1131
+ "required": true;
1132
+ "isSignal": true;
1133
+ };
1134
+ "size": {
1135
+ "alias": "size";
1136
+ "required": false;
1137
+ "isSignal": true;
1138
+ };
1139
+ }, {}, never, never, true, never>;
1140
+ }
1141
+ /** Prints a {@link Message}: its text as is, or its key translated. */
1142
+ export declare class WorkspaceMessage {
1143
+ readonly message: import("@angular/core").InputSignal<Message>;
1144
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceMessage, never>;
1145
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceMessage, "workspace-message", never, {
1146
+ "message": {
1147
+ "alias": "message";
1148
+ "required": true;
1149
+ "isSignal": true;
1150
+ };
1151
+ }, {}, never, never, true, never>;
1152
+ }
1153
+ /** A workspace's folder on a tile tinted with the accent derived from its id (see `workspaceColorFor()`). */
1154
+ export declare class WorkspaceIcon {
1155
+ readonly color: import("@angular/core").InputSignal<WorkspaceColor>;
1156
+ /** `lg` (38px) on a card, `md` (32px) in a row. */
1157
+ readonly size: import("@angular/core").InputSignal<"md" | "lg">;
1158
+ protected readonly classes: import("@angular/core").Signal<string>;
1159
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceIcon, never>;
1160
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceIcon, "workspace-icon", never, {
1161
+ "color": {
1162
+ "alias": "color";
1163
+ "required": false;
1164
+ "isSignal": true;
1165
+ };
1166
+ "size": {
1167
+ "alias": "size";
1168
+ "required": false;
1169
+ "isSignal": true;
1170
+ };
1171
+ }, {}, never, never, true, never>;
1172
+ }
1173
+ interface UploadDialogConfig {
1174
+ /** The workspace the files go to. */
1175
+ workspace: Pick<Workspace, "id" | "name" | "currentUserRole">;
1176
+ /** The tus endpoint — the host's `upload-endpoint`. */
1177
+ endpoint: string;
1178
+ }
1179
+ /**
1180
+ * "Upload Documents": stage files by dropping or picking them, then send them all
1181
+ * as one batch.
1182
+ *
1183
+ * Staging only — nothing leaves the browser until Upload is pressed, so the whole
1184
+ * selection goes out as ONE ingest job instead of each drop starting its own.
1185
+ * Dismissing the dialog any other way discards the selection: nothing was sent,
1186
+ * so there is nothing to keep.
1187
+ *
1188
+ * ```ts
1189
+ * await UploadDocuments.call({ workspace, endpoint });
1190
+ * ```
1191
+ *
1192
+ * Refuses to open for someone who cannot write to the workspace: the Upload
1193
+ * button is hidden for them, this is the belt to that braces.
1194
+ */
1195
+ export declare class UploadDialog {
1196
+ protected readonly call: import("@sinequa/galactik").CallRef<UploadDialogConfig, DialogResult>;
1197
+ private readonly dialog;
1198
+ private readonly uploads;
1199
+ protected readonly files: import("@angular/core").WritableSignal<File[]>;
1200
+ /** Whether there is anything to send. */
1201
+ protected readonly ready: import("@angular/core").Signal<boolean>;
1202
+ constructor();
1203
+ /**
1204
+ * Adds files to the staged selection. No gatekeeping here either: no type, count
1205
+ * or size check. Same-name files are still collapsed, and for a hard reason: a
1206
+ * document is addressed as `<jobId>/<fileName>`, so two files sharing a name
1207
+ * inside one job would collide on the server, not just in the list.
1208
+ */
1209
+ protected stage(files: File[]): void;
1210
+ protected upload(): void;
1211
+ static ɵfac: i0.ɵɵFactoryDeclaration<UploadDialog, never>;
1212
+ static ɵcmp: i0.ɵɵComponentDeclaration<UploadDialog, "upload-dialog", never, {}, {}, never, never, true, never>;
1213
+ }
1214
+ /** Imperative entry point: `await UploadDocuments.call({ workspace, endpoint })`. */
1215
+ export declare const UploadDocuments: import("@sinequa/galactik").Callable<UploadDialogConfig, DialogResult>;
1216
+ /** The files staged for upload, before anything is sent. */
1217
+ export declare class UploadFileList {
1218
+ readonly files: import("@angular/core").InputSignal<readonly File[]>;
1219
+ protected typeOf(file: File): import("@sinequa/atomic-angular/document-workspaces").DocumentType;
1220
+ static ɵfac: i0.ɵɵFactoryDeclaration<UploadFileList, never>;
1221
+ static ɵcmp: i0.ɵɵComponentDeclaration<UploadFileList, "upload-file-list", never, {
1222
+ "files": {
1223
+ "alias": "files";
1224
+ "required": true;
1225
+ "isSignal": true;
1226
+ };
1227
+ }, {}, never, never, true, never>;
1228
+ }
1229
+ /**
1230
+ * What the tracker says when the connection or the session is in trouble: what
1231
+ * happened, what is being done about it, and a countdown.
1232
+ *
1233
+ * It exists only while there is something to say, so its one-second clock only
1234
+ * runs then.
1235
+ */
1236
+ export declare class ConnectionBanner {
1237
+ readonly notice: import("@angular/core").InputSignal<ConnectionNotice>;
1238
+ /** The Retry button was pressed — offered once signed out, when the user can act. */
1239
+ readonly retry: import("@angular/core").OutputEmitterRef<void>;
1240
+ protected readonly retryLabel: Message;
1241
+ private readonly now;
1242
+ constructor();
1243
+ protected readonly title: import("@angular/core").Signal<Message>;
1244
+ protected readonly detail: import("@angular/core").Signal<Message>;
1245
+ /** "Next try in 0:03 · lost 0:12 ago · gives up in 4:40", the parts that apply. */
1246
+ protected readonly countdown: import("@angular/core").Signal<Message[]>;
1247
+ protected readonly showRetry: import("@angular/core").Signal<boolean>;
1248
+ static ɵfac: i0.ɵɵFactoryDeclaration<ConnectionBanner, never>;
1249
+ static ɵcmp: i0.ɵɵComponentDeclaration<ConnectionBanner, "connection-banner", never, {
1250
+ "notice": {
1251
+ "alias": "notice";
1252
+ "required": true;
1253
+ "isSignal": true;
1254
+ };
1255
+ }, {
1256
+ "retry": "retry";
1257
+ }, never, never, true, never>;
1258
+ }
1259
+ /**
1260
+ * - `inline`: a compact header and a line of counters;
1261
+ * - `expanded`: the files, section by section, workspace by workspace;
1262
+ * - `minimized`: slid down to the corner, only a strip showing; it still reports what happens, and slides back when hovered.
1263
+ */
1264
+ type Mode = "inline" | "expanded" | "minimized";
1265
+ type Tone = "normal" | "flash" | "success";
1266
+ interface TrackedWorkspace {
1267
+ id: string;
1268
+ name: string;
1269
+ }
1270
+ /**
1271
+ * The floating panel that follows files from the first byte sent to the end of
1272
+ * indexing: what is moving, what is done, what failed and why, and what can be
1273
+ * done about it (cancel a transfer, retry a failed one, dismiss a finished one).
1274
+ *
1275
+ * It only reads `UploadTrackerStore` — the state — and asks `UploadService` to
1276
+ * act. It shows itself when files are tracked and, once every file is indexed
1277
+ * without a failure, celebrates and dismisses itself.
1278
+ *
1279
+ * The moments that pass by themselves (a file just finished, a workspace just
1280
+ * completed, a connection just dropped) are noticed from changes in the store,
1281
+ * so they work the same whether `UploadService` or a demo page drives it.
1282
+ */
1283
+ export declare class UploadTracker {
1284
+ protected readonly store: {
1285
+ uploads: import("@angular/core").Signal<TrackedUpload[]>;
1286
+ workspaces: import("@angular/core").Signal<import("@sinequa/atomic-angular/document-workspaces").TrackerWorkspace[]>;
1287
+ connection: import("@ngrx/signals").DeepSignal<import("@sinequa/atomic-angular/document-workspaces").ConnectionNotice> | import("@angular/core").Signal<null>;
1288
+ clearedCount: import("@angular/core").Signal<number>;
1289
+ jobIndexing: import("@angular/core").Signal<Record<string, number>>;
1290
+ ignoredJobs: import("@angular/core").Signal<Record<string, true>>;
1291
+ active: import("@angular/core").Signal<boolean>;
1292
+ failed: import("@angular/core").Signal<TrackedUpload[]>;
1293
+ byStatus: import("@angular/core").Signal<Record<UploadStatus, TrackedUpload[]>>;
1294
+ allDone: import("@angular/core").Signal<boolean>;
1295
+ settled: import("@angular/core").Signal<boolean>;
1296
+ authorFailed: import("@angular/core").Signal<boolean>;
1297
+ progress: import("@angular/core").Signal<Record<string, PhaseProgress>>;
1298
+ indexedCount: import("@angular/core").Signal<number>;
1299
+ track: (files: import("@sinequa/atomic-angular/document-workspaces").TrackFileInput[], workspaceId: string, workspaceName: string) => void;
1300
+ setProgress: (id: string, percent: number) => void;
1301
+ setIndexing: (id: string) => void;
1302
+ setDone: (id: string) => void;
1303
+ setError: (id: string, options?: {
1304
+ message?: string;
1305
+ retryable?: boolean;
1306
+ }) => void;
1307
+ restart: (id: string) => void;
1308
+ applyJobs: (jobs: import("@sinequa/atomic").UploadJobStatus[]) => boolean;
1309
+ applyRemoteJobs: (entries: RemoteJobEntry[]) => boolean;
1310
+ setConnection: (connection: import("@sinequa/atomic-angular/document-workspaces").ConnectionNotice | null) => void;
1311
+ remove: (id: string) => void;
1312
+ removeWorkspace: (workspaceId: string) => void;
1313
+ reset: () => void;
1314
+ } & import("@ngrx/signals").StateSource<{
1315
+ uploads: TrackedUpload[];
1316
+ workspaces: import("@sinequa/atomic-angular/document-workspaces").TrackerWorkspace[];
1317
+ connection: import("@sinequa/atomic-angular/document-workspaces").ConnectionNotice | null;
1318
+ clearedCount: number;
1319
+ jobIndexing: Record<string, number>;
1320
+ ignoredJobs: Record<string, true>;
1321
+ }>;
1322
+ private readonly service;
1323
+ protected readonly mode: import("@angular/core").WritableSignal<Mode>;
1324
+ protected readonly workspaceIndex: import("@angular/core").WritableSignal<number>;
1325
+ /** Sections the user folded, keyed `<workspaceId>:<status>`; open by default. */
1326
+ private readonly folded;
1327
+ /** What has just happened, shown for a moment where the file count usually is. */
1328
+ private readonly flash;
1329
+ /** The same for a minimized panel, which has nowhere else to say it. */
1330
+ private readonly peek;
1331
+ protected readonly completingWorkspace: import("@angular/core").WritableSignal<TrackedWorkspace | null>;
1332
+ protected readonly dismissing: import("@angular/core").WritableSignal<boolean>;
1333
+ /** The pointer is over a minimized panel: it slides back up, to be clicked open. */
1334
+ private readonly hover;
1335
+ /** The panel is on its way down to the corner: quicker than the way back. */
1336
+ protected readonly minimizing: import("@angular/core").WritableSignal<boolean>;
1337
+ /** What the strip of a minimized panel shows, which is not always what it should: a change swaps it. */
1338
+ protected readonly peekShown: import("@angular/core").WritableSignal<Message>;
1339
+ protected readonly peekTone: import("@angular/core").WritableSignal<"error" | "success" | null>;
1340
+ protected readonly peekSwap: import("@angular/core").WritableSignal<"shown" | "hidden" | "below">;
1341
+ private peekKey;
1342
+ private hoverTimer;
1343
+ private readonly timers;
1344
+ private flashTimer;
1345
+ private peekTimer;
1346
+ private peekTimer2;
1347
+ private dismissTimer;
1348
+ private completeTimer;
1349
+ /** Status of each file as of the last change, to tell what just finished from what already had. */
1350
+ private previous;
1351
+ private previousPhase;
1352
+ protected readonly currentIndex: import("@angular/core").Signal<number>;
1353
+ protected readonly currentWorkspace: import("@angular/core").Signal<TrackedWorkspace | null>;
1354
+ protected readonly sections: import("@angular/core").Signal<{
1355
+ status: UploadStatus;
1356
+ uploads: TrackedUpload[];
1357
+ progress: number | undefined;
1358
+ }[]>;
1359
+ protected readonly header: import("@angular/core").Signal<{
1360
+ title: Message;
1361
+ subtitle: Message;
1362
+ tone: Tone;
1363
+ }>;
1364
+ /** The counters of the compact view: what moves, what finished, what failed. */
1365
+ protected readonly chips: import("@angular/core").Signal<{
1366
+ kind: string;
1367
+ message: Message;
1368
+ busy: boolean;
1369
+ }[]>;
1370
+ /** What the minimized pill says: a notification while there is one, otherwise a summary. */
1371
+ protected readonly pillMessage: import("@angular/core").Signal<Message>;
1372
+ protected readonly state: import("@angular/core").Signal<"minimized" | "normal" | "minimized-hover">;
1373
+ /** The strip's own colour: the failure, or the success, that stands. */
1374
+ protected readonly peekKind: import("@angular/core").Signal<"done" | "errors" | null>;
1375
+ protected readonly canRetryAll: import("@angular/core").Signal<boolean>;
1376
+ constructor();
1377
+ protected restore(): void;
1378
+ protected onPanelClick(): void;
1379
+ protected onPointerEnter(): void;
1380
+ protected onPointerLeave(): void;
1381
+ protected onTransitionEnd(event: TransitionEvent): void;
1382
+ private clearHover;
1383
+ protected toggleExpanded(): void;
1384
+ /** Minimizes while there is work going on; once everything is over (failures included), closes for good. */
1385
+ protected dismiss(): void;
1386
+ protected moveWorkspace(step: -1 | 1): void;
1387
+ protected isOpen(status: UploadStatus): boolean;
1388
+ protected toggleSection(status: UploadStatus): void;
1389
+ /**
1390
+ * Cancels a transfer in progress, or drops a row that is over. Only the
1391
+ * service knows the transfers it can still act on; a row it does not know
1392
+ * (finished, or failed on the server) is simply taken off the list.
1393
+ */
1394
+ protected discard(id: string): Promise<void>;
1395
+ protected retry(id: string): void;
1396
+ protected retryAll(): void;
1397
+ private onUploadsChanged;
1398
+ /**
1399
+ * Puts `message` on the strip: the old text leaves upward, the new one comes up from below.
1400
+ * The first text is simply there, and what is already shown is left alone.
1401
+ */
1402
+ private syncPeek;
1403
+ private showFlash;
1404
+ private notify;
1405
+ private completeWorkspace;
1406
+ private startAutoDismiss;
1407
+ private cancelAutoDismiss;
1408
+ private reset;
1409
+ private sectionKey;
1410
+ /** Replaces a pending timer with a new one, all cleared together when the panel is destroyed. */
1411
+ private replaceTimer;
1412
+ static ɵfac: i0.ɵɵFactoryDeclaration<UploadTracker, never>;
1413
+ static ɵcmp: i0.ɵɵComponentDeclaration<UploadTracker, "upload-tracker", never, {}, {}, never, never, true, never>;
1414
+ }
1415
+ interface SectionConfig {
1416
+ titleKey: string;
1417
+ /** Shows the average progress as a bar, and animated dots next to the title. */
1418
+ progress: boolean;
1419
+ /** Can be folded away — only the two sections that move. */
1420
+ foldable: boolean;
1421
+ }
1422
+ /**
1423
+ * The files of one status, for one workspace, in the tracker's expanded body:
1424
+ * a title, an average progress bar while they move, and a row per file with what
1425
+ * can be done to it.
1426
+ *
1427
+ * The files fold away and open again, and the bar follows the progress, with a
1428
+ * transition rather than a jump.
1429
+ *
1430
+ * It only reports what the user asked for; `UploadTracker` does it.
1431
+ */
1432
+ export declare class UploadTrackerSection {
1433
+ readonly status: import("@angular/core").InputSignal<UploadStatus>;
1434
+ readonly uploads: import("@angular/core").InputSignal<readonly TrackedUpload[]>;
1435
+ readonly open: import("@angular/core").InputSignal<boolean>;
1436
+ /** The phase's overall progress, when the tracker knows better than the mean of the files listed here (those that moved on are not). */
1437
+ readonly progress: import("@angular/core").InputSignal<number | undefined>;
1438
+ readonly toggle: import("@angular/core").OutputEmitterRef<void>;
1439
+ /** Cancel an upload in progress, or dismiss a finished row. The tracker tells the two apart. */
1440
+ readonly discard: import("@angular/core").OutputEmitterRef<string>;
1441
+ readonly retry: import("@angular/core").OutputEmitterRef<string>;
1442
+ protected readonly config: import("@angular/core").Signal<SectionConfig>;
1443
+ /** A failure, or a file the server will not index: both are in the error colours. */
1444
+ protected readonly failed: import("@angular/core").Signal<boolean>;
1445
+ /** The bar: the phase's overall progress, or else the mean progress of the files in the section. */
1446
+ protected readonly average: import("@angular/core").Signal<number>;
1447
+ /** The translation key standing for a row with no file name: a job started elsewhere. Only read when `name` is empty. */
1448
+ protected label(upload: TrackedUpload): string;
1449
+ protected round(value: number): number;
1450
+ static ɵfac: i0.ɵɵFactoryDeclaration<UploadTrackerSection, never>;
1451
+ static ɵcmp: i0.ɵɵComponentDeclaration<UploadTrackerSection, "upload-tracker-section", never, {
1452
+ "status": {
1453
+ "alias": "status";
1454
+ "required": true;
1455
+ "isSignal": true;
1456
+ };
1457
+ "uploads": {
1458
+ "alias": "uploads";
1459
+ "required": true;
1460
+ "isSignal": true;
1461
+ };
1462
+ "open": {
1463
+ "alias": "open";
1464
+ "required": false;
1465
+ "isSignal": true;
1466
+ };
1467
+ "progress": {
1468
+ "alias": "progress";
1469
+ "required": false;
1470
+ "isSignal": true;
1471
+ };
1472
+ }, {
1473
+ "toggle": "toggle";
1474
+ "discard": "discard";
1475
+ "retry": "retry";
1476
+ }, never, never, true, never>;
1477
+ }
1478
+ type ViewMode$1 = "grid" | "list";
1479
+ /**
1480
+ * The home of the workspaces view: every workspace you can reach, as a grid or a
1481
+ * list, filtered by tab and by search, with multi-selection and deletion.
1482
+ *
1483
+ * It reads `WorkspacesStore` and does not know how a workspace is opened or
1484
+ * created — it says so (`opened`, `createRequested`) and whoever hosts it decides.
1485
+ *
1486
+ * Each time it is shown it refreshes: workspaces created by others since the last
1487
+ * time appear, and the document counts match the server. The list already on
1488
+ * screen stays until the answer lands, and a failure leaves it untouched behind
1489
+ * an inline error. While it stays shown, it keeps polling in the background
1490
+ * (`injectLiveRefresh`) so a workspace created by someone else — one the current
1491
+ * user only reads, say — appears on its own, with no action needed.
1492
+ */
1493
+ export declare class WorkspaceList {
1494
+ /** A workspace was chosen for opening. */
1495
+ readonly opened: import("@angular/core").OutputEmitterRef<Workspace>;
1496
+ /** The user wants a new workspace. Creating one is the host's dialog. */
1497
+ readonly createRequested: import("@angular/core").OutputEmitterRef<void>;
1498
+ protected readonly store: {
1499
+ workspaces: import("@angular/core").Signal<Workspace[]>;
1500
+ loaded: import("@angular/core").Signal<boolean>;
1501
+ loading: import("@angular/core").Signal<boolean>;
1502
+ pagination: import("@ngrx/signals").DeepSignal<import("@sinequa/atomic-angular/document-workspaces").Pagination>;
1503
+ documents: import("@angular/core").Signal<Record<string, import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument[]>>;
1504
+ documentPagination: import("@angular/core").Signal<Record<string, import("@sinequa/atomic-angular/document-workspaces").Pagination>>;
1505
+ selectedIds: import("@angular/core").Signal<string[]>;
1506
+ hasMore: import("@angular/core").Signal<boolean>;
1507
+ truncated: import("@angular/core").Signal<boolean>;
1508
+ selectedCount: import("@angular/core").Signal<number>;
1509
+ selectedIdSet: import("@angular/core").Signal<Set<string>>;
1510
+ workspace: (id: string) => Workspace | undefined;
1511
+ documentsOf: (workspaceId: string) => import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument[];
1512
+ hasMoreDocuments: (workspaceId: string) => boolean;
1513
+ ensureLoaded: () => Promise<Workspace[]>;
1514
+ reload: () => Promise<Workspace[]>;
1515
+ loadMore: () => Promise<Workspace[]>;
1516
+ fetch: (id: string) => Promise<Workspace>;
1517
+ create: ({ name, description, tags, owners, readers }: import("@sinequa/atomic-angular/document-workspaces").CreateWorkspaceInput) => Promise<Workspace>;
1518
+ update: (id: string, patch: import("@sinequa/atomic").UpdateContainerInput) => Promise<Workspace | null>;
1519
+ remove: (ids: string[]) => Promise<import("@sinequa/atomic-angular/document-workspaces").RemoveWorkspacesResult>;
1520
+ forget: (id: string) => void;
1521
+ loadDocuments: (workspaceId: string, signal?: AbortSignal) => Promise<import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument[]>;
1522
+ loadMoreDocuments: (workspaceId: string) => Promise<import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument[]>;
1523
+ addDocuments: (workspaceId: string, documents: import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument[]) => void;
1524
+ discardDocuments: (workspaceId: string, documentIds: string[]) => void;
1525
+ patchDocument: (workspaceId: string, documentId: string, patch: Partial<import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument>) => void;
1526
+ saveDocuments: (workspaceId: string, documents: Pick<import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument, "name">[]) => Promise<import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument[]>;
1527
+ removeDocuments: (workspaceId: string, documentIds: string[]) => Promise<void>;
1528
+ moveDocuments: (fromWorkspaceId: string, toWorkspaceId: string, documentIds: string[]) => Promise<void>;
1529
+ toggleSelected: (id: string, checked: boolean) => void;
1530
+ clearSelection: () => void;
1531
+ } & import("@ngrx/signals").StateSource<{
1532
+ workspaces: Workspace[];
1533
+ loaded: boolean;
1534
+ loading: boolean;
1535
+ pagination: import("@sinequa/atomic-angular/document-workspaces").Pagination;
1536
+ documents: Record<string, import("@sinequa/atomic-angular/document-workspaces").WorkspaceDocument[]>;
1537
+ documentPagination: Record<string, import("@sinequa/atomic-angular/document-workspaces").Pagination>;
1538
+ selectedIds: string[];
1539
+ }>;
1540
+ private readonly uploads;
1541
+ private readonly injector;
1542
+ private readonly transloco;
1543
+ protected readonly tabs: readonly {
1544
+ tab: WorkspaceTab;
1545
+ labelKey: string;
1546
+ }[];
1547
+ protected readonly viewMode: import("@angular/core").WritableSignal<ViewMode$1>;
1548
+ protected readonly status: import("@angular/core").WritableSignal<"error" | "loading" | "ready">;
1549
+ protected readonly loadError: import("@angular/core").WritableSignal<Message | null>;
1550
+ protected readonly refreshError: import("@angular/core").WritableSignal<string | null>;
1551
+ protected readonly loadingMore: import("@angular/core").WritableSignal<boolean>;
1552
+ protected readonly loadMoreError: import("@angular/core").WritableSignal<string | null>;
1553
+ protected readonly filter: {
1554
+ tab: import("@angular/core").WritableSignal<WorkspaceTab>;
1555
+ query: import("@angular/core").WritableSignal<string>;
1556
+ counts: import("@angular/core").Signal<import("@sinequa/atomic-angular/document-workspaces").WorkspaceTabCounts>;
1557
+ filtered: import("@angular/core").Signal<Workspace[]>;
1558
+ clearQuery: () => void;
1559
+ };
1560
+ /** The search box, bound to the filter's own query signal. */
1561
+ protected readonly searchForm: import("@angular/forms/signals").FieldTree<string, string | number, "writable">;
1562
+ protected readonly selection: import("@sinequa/atomic-angular/document-workspaces").Selection<Workspace>;
1563
+ private readonly liveRefresh;
1564
+ constructor();
1565
+ /** Shows what is cached at once and refreshes behind it, or loads for the first time. */
1566
+ protected load(): Promise<void>;
1567
+ private refresh;
1568
+ protected loadMore(): Promise<void>;
1569
+ protected onTabChange(tab: string | undefined): void;
1570
+ protected deleteSelected(): void;
1571
+ /**
1572
+ * Asks, then deletes. The API only has a bulk DELETE, so one workspace is a
1573
+ * one-element batch. If some could not be deleted the dialog stays open and says
1574
+ * which, and a retry only targets those, so it does not re-attempt ones already gone.
1575
+ */
1576
+ protected confirmDelete(targets: Workspace[]): Promise<void>;
1577
+ private translate;
1578
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceList, never>;
1579
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceList, "workspace-list", never, {}, {
1580
+ "opened": "opened";
1581
+ "createRequested": "createRequested";
1582
+ }, never, never, true, never>;
1583
+ }
1584
+ /**
1585
+ * A workspace in the grid.
1586
+ *
1587
+ * It only reports what the user asked for — open it, delete it, tick it — and
1588
+ * leaves doing it to the list. Opening is on the title, a real button, so the
1589
+ * keyboard reaches it; the whole card is clickable as a convenience for the pointer.
1590
+ */
1591
+ export declare class WorkspaceCard {
1592
+ readonly workspace: import("@angular/core").InputSignal<Workspace>;
1593
+ /** The search box's text, to mark in the name. */
1594
+ readonly query: import("@angular/core").InputSignal<string>;
1595
+ /** The list is in selection mode: checkboxes show, and a click ticks instead of opening. */
1596
+ readonly selecting: import("@angular/core").InputSignal<boolean>;
1597
+ readonly selected: import("@angular/core").InputSignal<boolean>;
1598
+ /** Whether this workspace may be ticked — not one shared read-only. */
1599
+ readonly selectable: import("@angular/core").InputSignal<boolean>;
1600
+ readonly opened: import("@angular/core").OutputEmitterRef<void>;
1601
+ readonly deleteRequested: import("@angular/core").OutputEmitterRef<void>;
1602
+ readonly selectedChange: import("@angular/core").OutputEmitterRef<boolean>;
1603
+ /** Opens the workspace — or, in selection mode, ticks it. */
1604
+ protected activate(): void;
1605
+ protected onCardClick(event: MouseEvent): void;
1606
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceCard, never>;
1607
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceCard, "workspace-card", never, {
1608
+ "workspace": {
1609
+ "alias": "workspace";
1610
+ "required": true;
1611
+ "isSignal": true;
1612
+ };
1613
+ "query": {
1614
+ "alias": "query";
1615
+ "required": false;
1616
+ "isSignal": true;
1617
+ };
1618
+ "selecting": {
1619
+ "alias": "selecting";
1620
+ "required": false;
1621
+ "isSignal": true;
1622
+ };
1623
+ "selected": {
1624
+ "alias": "selected";
1625
+ "required": false;
1626
+ "isSignal": true;
1627
+ };
1628
+ "selectable": {
1629
+ "alias": "selectable";
1630
+ "required": false;
1631
+ "isSignal": true;
1632
+ };
1633
+ }, {
1634
+ "opened": "opened";
1635
+ "deleteRequested": "deleteRequested";
1636
+ "selectedChange": "selectedChange";
1637
+ }, never, never, true, never>;
1638
+ }
1639
+ /**
1640
+ * The columns of the list view — the header and every row share it.
1641
+ * Written out in full: Tailwind only generates the classes it can read as literals.
1642
+ */
1643
+ export declare const WORKSPACE_ROW_COLUMNS = "grid grid-cols-[minmax(0,1fr)_auto_auto_2.5rem] items-center gap-x-4 px-3 data-selecting:grid-cols-[2rem_minmax(0,1fr)_auto_auto_2.5rem] @xl:grid-cols-[minmax(0,1fr)_4.5rem_6.5rem_6.5rem_2.5rem] @xl:data-selecting:grid-cols-[2rem_minmax(0,1fr)_4.5rem_6.5rem_6.5rem_2.5rem] @3xl:grid-cols-[minmax(0,2fr)_5.5rem_7.5rem_9rem_minmax(5rem,1fr)_2.5rem] @3xl:data-selecting:grid-cols-[2rem_minmax(0,2fr)_5.5rem_7.5rem_9rem_minmax(5rem,1fr)_2.5rem]";
1644
+ /** A workspace in the list view. Same contract as {@link WorkspaceCard}. */
1645
+ export declare class WorkspaceRow {
1646
+ readonly workspace: import("@angular/core").InputSignal<Workspace>;
1647
+ readonly query: import("@angular/core").InputSignal<string>;
1648
+ readonly selecting: import("@angular/core").InputSignal<boolean>;
1649
+ readonly selected: import("@angular/core").InputSignal<boolean>;
1650
+ readonly selectable: import("@angular/core").InputSignal<boolean>;
1651
+ readonly opened: import("@angular/core").OutputEmitterRef<void>;
1652
+ readonly deleteRequested: import("@angular/core").OutputEmitterRef<void>;
1653
+ readonly selectedChange: import("@angular/core").OutputEmitterRef<boolean>;
1654
+ protected activate(): void;
1655
+ protected onRowClick(event: MouseEvent): void;
1656
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceRow, never>;
1657
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceRow, "workspace-row", never, {
1658
+ "workspace": {
1659
+ "alias": "workspace";
1660
+ "required": true;
1661
+ "isSignal": true;
1662
+ };
1663
+ "query": {
1664
+ "alias": "query";
1665
+ "required": false;
1666
+ "isSignal": true;
1667
+ };
1668
+ "selecting": {
1669
+ "alias": "selecting";
1670
+ "required": false;
1671
+ "isSignal": true;
1672
+ };
1673
+ "selected": {
1674
+ "alias": "selected";
1675
+ "required": false;
1676
+ "isSignal": true;
1677
+ };
1678
+ "selectable": {
1679
+ "alias": "selectable";
1680
+ "required": false;
1681
+ "isSignal": true;
1682
+ };
1683
+ }, {
1684
+ "opened": "opened";
1685
+ "deleteRequested": "deleteRequested";
1686
+ "selectedChange": "selectedChange";
1687
+ }, never, never, true, never>;
1688
+ }
1689
+ /**
1690
+ * A document in the grid.
1691
+ *
1692
+ * Like `WorkspaceCard` it only reports what the user asked for — open it,
1693
+ * delete it, tick it — and leaves doing it to the detail view. The name is a real
1694
+ * button so the keyboard reaches it; the whole card is clickable for the pointer.
1695
+ */
1696
+ export declare class DocumentCard {
1697
+ readonly document: import("@angular/core").InputSignal<WorkspaceDocument>;
1698
+ /** The role of the current user in the workspace — the delete button is for writers. */
1699
+ readonly workspaceRole: import("@angular/core").InputSignal<WorkspaceRole | null | undefined>;
1700
+ /** The search box's text, to mark in the name. */
1701
+ readonly query: import("@angular/core").InputSignal<string>;
1702
+ /** The one open in the preview. */
1703
+ readonly active: import("@angular/core").InputSignal<boolean>;
1704
+ readonly selecting: import("@angular/core").InputSignal<boolean>;
1705
+ readonly selected: import("@angular/core").InputSignal<boolean>;
1706
+ readonly selectable: import("@angular/core").InputSignal<boolean>;
1707
+ readonly opened: import("@angular/core").OutputEmitterRef<void>;
1708
+ readonly deleteRequested: import("@angular/core").OutputEmitterRef<void>;
1709
+ readonly selectedChange: import("@angular/core").OutputEmitterRef<boolean>;
1710
+ protected readonly labels: import("@sinequa/atomic-angular/document-workspaces").DocumentLabels;
1711
+ /** Opens the document — or, in selection mode, ticks it. */
1712
+ protected activate(): void;
1713
+ protected onCardClick(event: MouseEvent): void;
1714
+ static ɵfac: i0.ɵɵFactoryDeclaration<DocumentCard, never>;
1715
+ static ɵcmp: i0.ɵɵComponentDeclaration<DocumentCard, "document-card", never, {
1716
+ "document": {
1717
+ "alias": "document";
1718
+ "required": true;
1719
+ "isSignal": true;
1720
+ };
1721
+ "workspaceRole": {
1722
+ "alias": "workspaceRole";
1723
+ "required": true;
1724
+ "isSignal": true;
1725
+ };
1726
+ "query": {
1727
+ "alias": "query";
1728
+ "required": false;
1729
+ "isSignal": true;
1730
+ };
1731
+ "active": {
1732
+ "alias": "active";
1733
+ "required": false;
1734
+ "isSignal": true;
1735
+ };
1736
+ "selecting": {
1737
+ "alias": "selecting";
1738
+ "required": false;
1739
+ "isSignal": true;
1740
+ };
1741
+ "selected": {
1742
+ "alias": "selected";
1743
+ "required": false;
1744
+ "isSignal": true;
1745
+ };
1746
+ "selectable": {
1747
+ "alias": "selectable";
1748
+ "required": false;
1749
+ "isSignal": true;
1750
+ };
1751
+ }, {
1752
+ "opened": "opened";
1753
+ "deleteRequested": "deleteRequested";
1754
+ "selectedChange": "selectedChange";
1755
+ }, never, never, true, never>;
1756
+ }
1757
+ /**
1758
+ * The columns of the document list — the header and every row share it.
1759
+ * Written out in full: Tailwind only generates the classes it can read as literals.
1760
+ */
1761
+ export declare const DOCUMENT_ROW_COLUMNS = "grid grid-cols-[minmax(0,1fr)_auto_2.5rem] items-center gap-x-4 px-3 data-selecting:grid-cols-[2rem_minmax(0,1fr)_auto_2.5rem] @3xl:grid-cols-[minmax(0,2fr)_minmax(0,1fr)_6rem_10rem_2.5rem] @3xl:data-selecting:grid-cols-[2rem_minmax(0,2fr)_minmax(0,1fr)_6rem_10rem_2.5rem]";
1762
+ /** A document in the list view. Same contract as `DocumentCard`. */
1763
+ export declare class DocumentRow {
1764
+ readonly document: import("@angular/core").InputSignal<WorkspaceDocument>;
1765
+ readonly workspaceRole: import("@angular/core").InputSignal<WorkspaceRole | null | undefined>;
1766
+ readonly query: import("@angular/core").InputSignal<string>;
1767
+ readonly active: import("@angular/core").InputSignal<boolean>;
1768
+ readonly selecting: import("@angular/core").InputSignal<boolean>;
1769
+ readonly selected: import("@angular/core").InputSignal<boolean>;
1770
+ readonly selectable: import("@angular/core").InputSignal<boolean>;
1771
+ readonly opened: import("@angular/core").OutputEmitterRef<void>;
1772
+ readonly deleteRequested: import("@angular/core").OutputEmitterRef<void>;
1773
+ readonly selectedChange: import("@angular/core").OutputEmitterRef<boolean>;
1774
+ protected readonly labels: import("@sinequa/atomic-angular/document-workspaces").DocumentLabels;
1775
+ protected activate(): void;
1776
+ protected onRowClick(event: MouseEvent): void;
1777
+ static ɵfac: i0.ɵɵFactoryDeclaration<DocumentRow, never>;
1778
+ static ɵcmp: i0.ɵɵComponentDeclaration<DocumentRow, "document-row", never, {
1779
+ "document": {
1780
+ "alias": "document";
1781
+ "required": true;
1782
+ "isSignal": true;
1783
+ };
1784
+ "workspaceRole": {
1785
+ "alias": "workspaceRole";
1786
+ "required": true;
1787
+ "isSignal": true;
1788
+ };
1789
+ "query": {
1790
+ "alias": "query";
1791
+ "required": false;
1792
+ "isSignal": true;
1793
+ };
1794
+ "active": {
1795
+ "alias": "active";
1796
+ "required": false;
1797
+ "isSignal": true;
1798
+ };
1799
+ "selecting": {
1800
+ "alias": "selecting";
1801
+ "required": false;
1802
+ "isSignal": true;
1803
+ };
1804
+ "selected": {
1805
+ "alias": "selected";
1806
+ "required": false;
1807
+ "isSignal": true;
1808
+ };
1809
+ "selectable": {
1810
+ "alias": "selectable";
1811
+ "required": false;
1812
+ "isSignal": true;
1813
+ };
1814
+ }, {
1815
+ "opened": "opened";
1816
+ "deleteRequested": "deleteRequested";
1817
+ "selectedChange": "selectedChange";
1818
+ }, never, never, true, never>;
1819
+ }
1820
+ /**
1821
+ * One person of the access list.
1822
+ *
1823
+ * Two rows never get a role menu: the creator's (immutable for the API) and your own
1824
+ * (an owner who demotes or removes themselves loses the workspace with no way back).
1825
+ * Removing asks first, inline in the row.
1826
+ */
1827
+ export declare class MemberRow {
1828
+ readonly member: import("@angular/core").InputSignal<Member>;
1829
+ /** A change is in flight: nothing else can be asked meanwhile. */
1830
+ readonly disabled: import("@angular/core").InputSignal<boolean>;
1831
+ readonly roleChange: import("@angular/core").OutputEmitterRef<AssignableRole>;
1832
+ readonly removed: import("@angular/core").OutputEmitterRef<void>;
1833
+ protected readonly confirming: import("@angular/core").WritableSignal<boolean>;
1834
+ protected readonly assignable: import("@angular/core").Signal<AssignableRole>;
1835
+ static ɵfac: i0.ɵɵFactoryDeclaration<MemberRow, never>;
1836
+ static ɵcmp: i0.ɵɵComponentDeclaration<MemberRow, "member-row", never, {
1837
+ "member": {
1838
+ "alias": "member";
1839
+ "required": true;
1840
+ "isSignal": true;
1841
+ };
1842
+ "disabled": {
1843
+ "alias": "disabled";
1844
+ "required": false;
1845
+ "isSignal": true;
1846
+ };
1847
+ }, {
1848
+ "roleChange": "roleChange";
1849
+ "removed": "removed";
1850
+ }, never, never, true, never>;
1851
+ }
1852
+ /** A small round avatar: the initials of a person, a glyph for a group. */
1853
+ export declare class PrincipalAvatar {
1854
+ readonly name: import("@angular/core").InputSignal<string>;
1855
+ readonly group: import("@angular/core").InputSignal<boolean>;
1856
+ protected readonly initials: import("@angular/core").Signal<string>;
1857
+ static ɵfac: i0.ɵɵFactoryDeclaration<PrincipalAvatar, never>;
1858
+ static ɵcmp: i0.ɵɵComponentDeclaration<PrincipalAvatar, "principal-avatar", never, {
1859
+ "name": {
1860
+ "alias": "name";
1861
+ "required": true;
1862
+ "isSignal": true;
1863
+ };
1864
+ "group": {
1865
+ "alias": "group";
1866
+ "required": false;
1867
+ "isSignal": true;
1868
+ };
1869
+ }, {}, never, never, true, never>;
1870
+ }
1871
+ /**
1872
+ * Picks people: a tag-style box with search-as-you-type over the directory, a chip
1873
+ * per person, and the role the next ones get. The picked people are the `entries`
1874
+ * model — a parent owns them and can clear them.
1875
+ *
1876
+ * Groups never show up in the results: the API documents `owners`/`readers` as
1877
+ * *user* ids, so staging a group would look like it worked and grant nobody anything.
1878
+ *
1879
+ * The id of an entry is the principal's `userId` (`domain|id`), which is what a
1880
+ * workspace holds and the only form the API accepts.
1881
+ *
1882
+ * The search, the results popup (a combobox in the top layer, so a dialog's edges cannot clip it) and its keyboard
1883
+ * are those of the library's `Autocomplete`: this component adds what is specific to people — the avatar in a row, the
1884
+ * role of a chip, the role the next ones get. Like in a chat's mention box, Enter picks the first row and closes the
1885
+ * list.
1886
+ */
1887
+ export declare class PrincipalPicker {
1888
+ /** The people picked so far. */
1889
+ readonly entries: import("@angular/core").ModelSignal<PickedPrincipal[]>;
1890
+ /** Ids to keep out of the results — the people who already have access. */
1891
+ readonly excludeIds: import("@angular/core").InputSignal<readonly string[]>;
1892
+ /** Chips carry a toggle to switch their role. Off where everyone picked takes the selector's role. */
1893
+ readonly showChipRole: import("@angular/core").InputSignal<boolean>;
1894
+ private readonly directory;
1895
+ /** The role the next person picked gets. */
1896
+ protected readonly role: import("@angular/core").WritableSignal<AssignableRole>;
1897
+ /** Groups are not offered: see the class comment. */
1898
+ protected readonly search: (query: string, { signal }: {
1899
+ signal: AbortSignal;
1900
+ }) => Promise<SecurityPrincipal[]>;
1901
+ protected readonly principalKey: (person: SecurityPrincipal) => string;
1902
+ protected readonly entryKey: (entry: PickedPrincipal) => string;
1903
+ protected readonly entryLabel: (entry: PickedPrincipal) => string;
1904
+ protected readonly displayName: (person: SecurityPrincipal) => string;
1905
+ protected pick(person: SecurityPrincipal): void;
1906
+ protected toggleRole(index: number): void;
1907
+ protected removeEntry(entry: PickedPrincipal): void;
1908
+ static ɵfac: i0.ɵɵFactoryDeclaration<PrincipalPicker, never>;
1909
+ static ɵcmp: i0.ɵɵComponentDeclaration<PrincipalPicker, "principal-picker", never, {
1910
+ "entries": {
1911
+ "alias": "entries";
1912
+ "required": false;
1913
+ "isSignal": true;
1914
+ };
1915
+ "excludeIds": {
1916
+ "alias": "excludeIds";
1917
+ "required": false;
1918
+ "isSignal": true;
1919
+ };
1920
+ "showChipRole": {
1921
+ "alias": "showChipRole";
1922
+ "required": false;
1923
+ "isSignal": true;
1924
+ };
1925
+ }, {
1926
+ "entries": "entriesChange";
1927
+ }, never, never, true, never>;
1928
+ }
1929
+ /**
1930
+ * "Owner ▾" / "Reader ▾": a menu to pick a role, and — for someone who already has
1931
+ * access — to take it away.
1932
+ *
1933
+ * It only reports the choice; whoever uses it decides what it does.
1934
+ */
1935
+ export declare class RoleMenu {
1936
+ readonly role: import("@angular/core").InputSignal<AssignableRole>;
1937
+ /** Adds "Remove" to the menu. */
1938
+ readonly removable: import("@angular/core").InputSignal<boolean>;
1939
+ readonly disabled: import("@angular/core").InputSignal<boolean>;
1940
+ /** The accessible name of the button, already translated. */
1941
+ readonly label: import("@angular/core").InputSignal<string>;
1942
+ readonly roleChange: import("@angular/core").OutputEmitterRef<AssignableRole>;
1943
+ readonly removeRequested: import("@angular/core").OutputEmitterRef<void>;
1944
+ protected readonly roleKey: import("@angular/core").Signal<"documentWorkspaces.share.owner" | "documentWorkspaces.share.reader">;
1945
+ protected onSelect(value: string): void;
1946
+ static ɵfac: i0.ɵɵFactoryDeclaration<RoleMenu, never>;
1947
+ static ɵcmp: i0.ɵɵComponentDeclaration<RoleMenu, "role-menu", never, {
1948
+ "role": {
1949
+ "alias": "role";
1950
+ "required": true;
1951
+ "isSignal": true;
1952
+ };
1953
+ "removable": {
1954
+ "alias": "removable";
1955
+ "required": false;
1956
+ "isSignal": true;
1957
+ };
1958
+ "disabled": {
1959
+ "alias": "disabled";
1960
+ "required": false;
1961
+ "isSignal": true;
1962
+ };
1963
+ "label": {
1964
+ "alias": "label";
1965
+ "required": true;
1966
+ "isSignal": true;
1967
+ };
1968
+ }, {
1969
+ "roleChange": "roleChange";
1970
+ "removeRequested": "removeRequested";
1971
+ }, never, never, true, never>;
1972
+ }
1973
+ interface ShareDialogConfig {
1974
+ workspaceId: Workspace["id"];
1975
+ }
1976
+ /**
1977
+ * "Manage access": who can do what in a workspace.
1978
+ *
1979
+ * ```ts
1980
+ * await ShareWorkspace.call({ workspaceId });
1981
+ * ```
1982
+ *
1983
+ * Every change — a role, a removal, people added — is saved at once. The list reads
1984
+ * the workspace from `WorkspacesStore`, so it shows what the server now holds, and
1985
+ * stays in step with a change made elsewhere. Only the ones who can write to the
1986
+ * workspace get here; the server refuses the others anyway.
1987
+ */
1988
+ export declare class ShareDialog {
1989
+ protected readonly call: import("@sinequa/galactik").CallRef<ShareDialogConfig, DialogResult>;
1990
+ private readonly dialog;
1991
+ private readonly store;
1992
+ private readonly directory;
1993
+ private readonly currentUser;
1994
+ private readonly workspaceId;
1995
+ protected readonly workspace: import("@angular/core").Signal<Workspace | undefined>;
1996
+ protected readonly members: import("@angular/core").Signal<import("@sinequa/atomic-angular/document-workspaces").Member[]>;
1997
+ protected readonly memberIds: import("@angular/core").Signal<string[]>;
1998
+ /** The people picked, not added yet. */
1999
+ protected readonly staged: import("@angular/core").WritableSignal<PickedPrincipal[]>;
2000
+ protected readonly adding: import("@angular/core").WritableSignal<boolean>;
2001
+ protected readonly busy: import("@angular/core").WritableSignal<boolean>;
2002
+ protected readonly error: import("@angular/core").WritableSignal<WorkspaceErrorMessage | null>;
2003
+ constructor();
2004
+ protected add(): Promise<void>;
2005
+ protected change(id: string, role: AssignableRole | "remove"): Promise<void>;
2006
+ /** One write at a time: the arrays it sends are built from the workspace as it is now. */
2007
+ private save;
2008
+ static ɵfac: i0.ɵɵFactoryDeclaration<ShareDialog, never>;
2009
+ static ɵcmp: i0.ɵɵComponentDeclaration<ShareDialog, "share-dialog", never, {}, {}, never, never, true, never>;
2010
+ }
2011
+ /** Imperative entry point: `await ShareWorkspace.call({ workspaceId })`. */
2012
+ export declare const ShareWorkspace: import("@sinequa/galactik").Callable<ShareDialogConfig, DialogResult>;
2013
+ /** What "Ask follow-up" hands the host: what to show in its compose area, already translated. */
2014
+ interface AskFollowUp {
2015
+ title: string;
2016
+ type: WorkspaceDocument["type"];
2017
+ placeholder: string;
2018
+ suggestions: string[];
2019
+ }
2020
+ /**
2021
+ * The preview of a document: the converted copy the preview web service points at,
2022
+ * in a frame, with a zoom, a find-in-text and an "Ask follow-up".
2023
+ *
2024
+ * It asks the service for the document by id, as a search app does. Until the
2025
+ * backend hands out the index id for an uploaded document (the listing carries
2026
+ * the upload-side id) the service answers an error, and the panel says the document
2027
+ * cannot be previewed rather than showing a stand-in.
2028
+ *
2029
+ * Finding text needs to read the frame, so it only works when the copy is served
2030
+ * from the same origin as the page; otherwise the box is disabled and says why.
2031
+ */
2032
+ export declare class DocumentPreviewPanel {
2033
+ readonly document: import("@angular/core").InputSignal<WorkspaceDocument>;
2034
+ /** The query the preview web service renders the document for — the host's `query-name`. */
2035
+ readonly queryName: import("@angular/core").InputSignal<string>;
2036
+ readonly closed: import("@angular/core").OutputEmitterRef<void>;
2037
+ readonly askFollowUp: import("@angular/core").OutputEmitterRef<AskFollowUp>;
2038
+ private readonly sanitizer;
2039
+ private readonly transloco;
2040
+ private readonly lang;
2041
+ private readonly frame;
2042
+ protected readonly zoomMin = 0.25;
2043
+ protected readonly zoomMax = 2.5;
2044
+ protected readonly zoom: import("@angular/core").WritableSignal<number>;
2045
+ protected readonly status: import("@angular/core").WritableSignal<"loading" | "ready" | "unavailable">;
2046
+ protected readonly frameUrl: import("@angular/core").WritableSignal<SafeResourceUrl | null>;
2047
+ protected readonly titleUrl: import("@angular/core").WritableSignal<string | undefined>;
2048
+ protected readonly date: import("@angular/core").Signal<string | null>;
2049
+ /** The text of the frame, once it has loaded and the page is allowed to read it. */
2050
+ private readonly frameBody;
2051
+ protected readonly searchable: import("@angular/core").Signal<boolean>;
2052
+ protected readonly query: import("@angular/core").WritableSignal<string>;
2053
+ /** The find box, bound to `query`; off while the frame cannot be read. */
2054
+ protected readonly searchForm: import("@angular/forms/signals").FieldTree<string, string | number, "writable">;
2055
+ protected readonly search: import("@sinequa/atomic-angular/document-workspaces").TextSearch;
2056
+ /** A slow answer for a document the user has already left must not replace the one on screen. */
2057
+ private token;
2058
+ private readonly searchHost;
2059
+ constructor();
2060
+ private load;
2061
+ protected onFrameLoad(): void;
2062
+ protected zoomBy(direction: 1 | -1): void;
2063
+ protected onSearchKeydown(event: KeyboardEvent): void;
2064
+ protected askFollowUpClicked(): void;
2065
+ static ɵfac: i0.ɵɵFactoryDeclaration<DocumentPreviewPanel, never>;
2066
+ static ɵcmp: i0.ɵɵComponentDeclaration<DocumentPreviewPanel, "document-preview-panel", never, {
2067
+ "document": {
2068
+ "alias": "document";
2069
+ "required": true;
2070
+ "isSignal": true;
2071
+ };
2072
+ "queryName": {
2073
+ "alias": "queryName";
2074
+ "required": false;
2075
+ "isSignal": true;
2076
+ };
2077
+ }, {
2078
+ "closed": "closed";
2079
+ "askFollowUp": "askFollowUp";
2080
+ }, never, never, true, never>;
2081
+ }
2082
+ declare const TITLE_MAX_LENGTH = 120;
2083
+ declare const DESCRIPTION_MAX_LENGTH = 300;
2084
+ type WorkspaceEditDialogConfig = {
2085
+ mode: "add";
2086
+ } | {
2087
+ mode: "edit";
2088
+ workspace: Workspace;
2089
+ };
2090
+ /** What the dialog resolves with once it saved: the workspace as the server now has it. */
2091
+ type WorkspaceEditResult = {
2092
+ type: "dialog-confirm";
2093
+ workspace: Workspace;
2094
+ };
2095
+ /**
2096
+ * Creates a workspace, or edits the title, description and tags of one.
2097
+ *
2098
+ * ```ts
2099
+ * const result = await EditWorkspace.call({ mode: "add" });
2100
+ * if (typeof result === "object") open(result["workspace"]);
2101
+ * ```
2102
+ *
2103
+ * It saves by itself — on failure it stays open with the error, so the user can
2104
+ * retry — and while a request is in flight it cannot be dismissed. Editing never
2105
+ * sends `owners`/`readers`: the API replaces both wholesale, so sending a snapshot
2106
+ * taken when the dialog opened could undo what "Manage access" did since.
2107
+ */
2108
+ export declare class WorkspaceEditDialog {
2109
+ protected readonly call: import("@sinequa/galactik").CallRef<WorkspaceEditDialogConfig, DialogResult>;
2110
+ private readonly dialog;
2111
+ private readonly store;
2112
+ private readonly currentUser;
2113
+ protected readonly titleMax = 120;
2114
+ protected readonly descriptionMax = 300;
2115
+ private readonly config;
2116
+ protected readonly adding: import("@angular/core").Signal<boolean>;
2117
+ private readonly initial;
2118
+ private readonly model;
2119
+ private readonly tagInput;
2120
+ protected readonly editForm: FieldTree<{
2121
+ name: string;
2122
+ description: string;
2123
+ tags: string[];
2124
+ }, string | number, "writable">;
2125
+ protected readonly description: import("@angular/core").Signal<string>;
2126
+ /** The people picked to share a new workspace with. */
2127
+ protected readonly sharedWith: import("@angular/core").WritableSignal<PickedPrincipal[]>;
2128
+ /** Never offered: they are the creator-to-be, and go into `owners` when the workspace is created. */
2129
+ protected readonly excludedIds: import("@angular/core").Signal<string[]>;
2130
+ protected readonly busy: import("@angular/core").WritableSignal<boolean>;
2131
+ protected readonly error: import("@angular/core").WritableSignal<WorkspaceErrorMessage | null>;
2132
+ protected readonly descriptionTooLong: import("@angular/core").Signal<boolean>;
2133
+ protected readonly saveLabel: import("@angular/core").Signal<"documentWorkspaces.common.save" | "documentWorkspaces.edit.creating" | "documentWorkspaces.edit.saving" | "documentWorkspaces.common.create">;
2134
+ /** What to resolve with once the native dialog has closed: the saved workspace, or the plain dismissal. */
2135
+ private outcome;
2136
+ constructor();
2137
+ /** A field shows its error only once the user has left it, or tried to submit. */
2138
+ protected showError<T>(field: FieldTree<T>): boolean;
2139
+ /** The submission itself, only ever reached with a valid form: the title is there, the description fits. */
2140
+ private save;
2141
+ protected onClosed(event: DialogResult): void;
2142
+ /** Escape on a native <dialog> closes it: refuse while the request is running. */
2143
+ protected onNativeCancel(event: Event): void;
2144
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceEditDialog, never>;
2145
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceEditDialog, "workspace-edit-dialog", never, {}, {}, never, never, true, never>;
2146
+ }
2147
+ /** Imperative entry point: `await EditWorkspace.call({ mode: "add" })`. */
2148
+ export declare const EditWorkspace: import("@sinequa/galactik").Callable<WorkspaceEditDialogConfig, DialogResult>;
2149
+ type ViewMode = "grid" | "list";
2150
+ /**
2151
+ * One workspace and its documents, as a grid or a list, with search, multi-selection,
2152
+ * upload, edit and delete.
2153
+ *
2154
+ * It reads `WorkspacesStore` for both the workspace and its documents, so a change
2155
+ * made elsewhere — a role changed from "Manage access", a status patched by the
2156
+ * upload flow — shows without anything being pushed in. It does not know where it is
2157
+ * shown: it says what the user asked for (`back`, `deleted`, `askFollowUp`) and whoever
2158
+ * hosts it decides. The document open in the preview is the `previewedDocumentId` model.
2159
+ *
2160
+ * Only someone who can write sees the edit, share, delete, upload and select controls.
2161
+ *
2162
+ * While a workspace stays open, it keeps polling its documents and its own
2163
+ * metadata in the background (`injectLiveRefresh`), so an edit or a document
2164
+ * another user adds shows up on its own.
2165
+ */
2166
+ export declare class WorkspaceDetail {
2167
+ readonly workspaceId: import("@angular/core").InputSignal<string>;
2168
+ /** The tus endpoint uploads go to — the host's `upload-endpoint`. */
2169
+ readonly uploadEndpoint: import("@angular/core").InputSignal<string>;
2170
+ /** The document open in the preview, or `null`. Two-way: the host can open one, and is told when it is opened or closed. */
2171
+ readonly previewedDocumentId: import("@angular/core").ModelSignal<string | null>;
2172
+ /** The query the preview web service renders a document for — the host's `query-name`. */
2173
+ readonly queryName: import("@angular/core").InputSignal<string>;
2174
+ /** The user wants to leave for the list. */
2175
+ readonly back: import("@angular/core").OutputEmitterRef<void>;
2176
+ /** The workspace was deleted from here; the host has nothing left to show. */
2177
+ readonly deleted: import("@angular/core").OutputEmitterRef<Workspace>;
2178
+ /** The workspace was edited from here (title, description, tags). */
2179
+ readonly workspaceUpdated: import("@angular/core").OutputEmitterRef<Workspace>;
2180
+ /** "Ask follow-up" on a previewed document: the host's chat is what takes it from there. */
2181
+ readonly askFollowUp: import("@angular/core").OutputEmitterRef<AskFollowUp>;
2182
+ private readonly store;
2183
+ private readonly uploads;
2184
+ private readonly injector;
2185
+ private readonly transloco;
2186
+ protected readonly viewMode: import("@angular/core").WritableSignal<ViewMode>;
2187
+ protected readonly query: import("@angular/core").WritableSignal<string>;
2188
+ /** The search box, bound to `query`. */
2189
+ protected readonly searchForm: import("@angular/forms/signals").FieldTree<string, string | number, "writable">;
2190
+ protected readonly status: import("@angular/core").WritableSignal<"error" | "loading" | "ready">;
2191
+ protected readonly loadError: import("@angular/core").WritableSignal<Message | null>;
2192
+ protected readonly refreshError: import("@angular/core").WritableSignal<string | null>;
2193
+ protected readonly refreshing: import("@angular/core").WritableSignal<boolean>;
2194
+ protected readonly loadingMore: import("@angular/core").WritableSignal<boolean>;
2195
+ protected readonly loadMoreError: import("@angular/core").WritableSignal<string | null>;
2196
+ /** The background refresh found the workspace gone from the server (deleted elsewhere). */
2197
+ protected readonly deletedElsewhere: import("@angular/core").WritableSignal<boolean>;
2198
+ protected readonly workspace: import("@angular/core").Signal<Workspace | undefined>;
2199
+ protected readonly documents: import("@angular/core").Signal<WorkspaceDocument[]>;
2200
+ protected readonly previewed: import("@angular/core").Signal<WorkspaceDocument | null>;
2201
+ /** The width the user dragged the preview to, if they did. */
2202
+ protected readonly userPreviewWidth: import("@angular/core").WritableSignal<number | null>;
2203
+ /** The width of the view itself: the preview opens at 40% of it (280px at the least), as in the web element. */
2204
+ private readonly hostWidth;
2205
+ protected readonly previewWidth: import("@angular/core").Signal<number>;
2206
+ /** The list keeps at least 360px. */
2207
+ protected readonly maxPreviewWidth: import("@angular/core").Signal<number | undefined>;
2208
+ protected readonly hasMore: import("@angular/core").Signal<boolean>;
2209
+ protected readonly filtered: import("@angular/core").Signal<WorkspaceDocument[]>;
2210
+ private readonly writable;
2211
+ protected readonly selection: import("@sinequa/atomic-angular/document-workspaces").Selection<WorkspaceDocument>;
2212
+ private readonly liveRefresh;
2213
+ constructor();
2214
+ private open;
2215
+ /**
2216
+ * Leaving from here: once the background refresh found the workspace gone,
2217
+ * the cache still holds it (so its name kept showing) — drop it now, so a
2218
+ * stale card does not show it back on the list.
2219
+ */
2220
+ protected leave(): void;
2221
+ /** Reads the workspace (when it is not cached) and its documents, for the workspace currently shown. */
2222
+ protected load(): Promise<void>;
2223
+ /** Reads the documents again without leaving the view; a failure leaves the ones shown as they are. */
2224
+ protected refresh(): Promise<void>;
2225
+ protected loadMore(): Promise<void>;
2226
+ /** Opening the open one again closes it. */
2227
+ protected togglePreview(document: WorkspaceDocument): void;
2228
+ protected upload(workspace: Workspace): void;
2229
+ protected share(workspace: Workspace): void;
2230
+ protected edit(workspace: Workspace): Promise<void>;
2231
+ protected confirmDeleteWorkspace(workspace: Workspace): Promise<void>;
2232
+ protected deleteSelected(): void;
2233
+ protected confirmDeleteDocuments(targets: WorkspaceDocument[]): Promise<void>;
2234
+ private translate;
2235
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceDetail, never>;
2236
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceDetail, "workspace-detail", never, {
2237
+ "workspaceId": {
2238
+ "alias": "workspaceId";
2239
+ "required": true;
2240
+ "isSignal": true;
2241
+ };
2242
+ "uploadEndpoint": {
2243
+ "alias": "uploadEndpoint";
2244
+ "required": false;
2245
+ "isSignal": true;
2246
+ };
2247
+ "previewedDocumentId": {
2248
+ "alias": "previewedDocumentId";
2249
+ "required": false;
2250
+ "isSignal": true;
2251
+ };
2252
+ "queryName": {
2253
+ "alias": "queryName";
2254
+ "required": false;
2255
+ "isSignal": true;
2256
+ };
2257
+ }, {
2258
+ "previewedDocumentId": "previewedDocumentIdChange";
2259
+ "back": "back";
2260
+ "deleted": "deleted";
2261
+ "workspaceUpdated": "workspaceUpdated";
2262
+ "askFollowUp": "askFollowUp";
2263
+ }, never, never, true, never>;
2264
+ }
2265
+ /** What a host chat reads to build its own mention menu. */
2266
+ interface MentionSelection {
2267
+ ids: string[];
2268
+ count: number;
2269
+ }
2270
+ /**
2271
+ * The workspaces as rows to tick, for the "+" / "@" menu of a host chat.
2272
+ *
2273
+ * The ticks are the store's, not this component's: however many of these are on
2274
+ * screen (one per open menu), and wherever else the selection is read, they show the
2275
+ * same thing. Every workspace is listed, the ones shared read-only too — mentioning
2276
+ * one only reads it.
2277
+ */
2278
+ export declare class WorkspaceMentionList {
2279
+ /** The ticks changed because of this list. */
2280
+ readonly selectionChanged: import("@angular/core").OutputEmitterRef<MentionSelection>;
2281
+ private readonly store;
2282
+ protected readonly status: import("@angular/core").WritableSignal<"error" | "loading" | "ready">;
2283
+ /** The rows, for this list and for a host that draws its own. */
2284
+ readonly items: import("@angular/core").Signal<{
2285
+ id: string;
2286
+ name: string;
2287
+ color: import("@sinequa/atomic-angular/document-workspaces").WorkspaceColor;
2288
+ documentCount: number;
2289
+ selected: boolean;
2290
+ }[]>;
2291
+ constructor();
2292
+ /** Ticks a workspace from outside — a host that has its own way of choosing one. */
2293
+ select(id: string): void;
2294
+ protected toggle(id: string, checked: boolean): void;
2295
+ private announce;
2296
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspaceMentionList, never>;
2297
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspaceMentionList, "workspace-mention-list", never, {}, {
2298
+ "selectionChanged": "selectionChanged";
2299
+ }, never, never, true, never>;
2300
+ }
2301
+ /** What "save" attaches to a workspace: a document that already exists, by name. */
2302
+ interface PickerDocument {
2303
+ name: string;
2304
+ }
2305
+ export declare const NEW_WORKSPACE_NAME_MAX = 50;
2306
+ /**
2307
+ * Picks the workspace to put documents in — "Save to workspace", or "Move to
2308
+ * workspace" when they come from another one — and can make a new one on the spot.
2309
+ *
2310
+ * Only the workspaces the user can write to are offered: attaching a document is a
2311
+ * write, so a workspace shared read-only stays out (it stays visible wherever it is
2312
+ * merely read). When moving, the workspace the documents come from is left out too.
2313
+ *
2314
+ * It does the work (the store's `saveDocuments` / `moveDocuments`) and says when it is
2315
+ * over — `completed` — once the check mark has been seen; whoever shows it (a popover,
2316
+ * a bottom sheet) decides what closing means.
2317
+ */
2318
+ export declare class WorkspacePicker {
2319
+ /** `save` attaches `documents` to the chosen workspace; `move` takes `documentIds` from `fromWorkspaceId` to it. */
2320
+ readonly mode: import("@angular/core").InputSignal<"move" | "save">;
2321
+ readonly documents: import("@angular/core").InputSignal<readonly PickerDocument[]>;
2322
+ readonly fromWorkspaceId: import("@angular/core").InputSignal<string | null>;
2323
+ readonly documentIds: import("@angular/core").InputSignal<readonly string[]>;
2324
+ /** The documents are in the workspace, and the check mark has been seen. */
2325
+ readonly completed: import("@angular/core").OutputEmitterRef<Workspace>;
2326
+ private readonly store;
2327
+ protected readonly status: import("@angular/core").WritableSignal<"error" | "loading" | "ready">;
2328
+ protected readonly busyId: import("@angular/core").WritableSignal<string | null>;
2329
+ protected readonly doneId: import("@angular/core").WritableSignal<string | null>;
2330
+ protected readonly error: import("@angular/core").WritableSignal<Message | null>;
2331
+ protected readonly creating: import("@angular/core").WritableSignal<boolean>;
2332
+ /** The name of the workspace being made, in a form of its own. */
2333
+ private readonly newModel;
2334
+ protected readonly newForm: import("@angular/forms/signals").FieldTree<{
2335
+ name: string;
2336
+ }, string | number, "writable">;
2337
+ protected readonly newName: import("@angular/core").Signal<string>;
2338
+ protected readonly submitting: import("@angular/core").WritableSignal<boolean>;
2339
+ /** The workspaces made from here, newest first: they head the list. */
2340
+ private readonly created;
2341
+ private readonly candidates;
2342
+ protected readonly rows: import("@angular/core").Signal<Workspace[]>;
2343
+ protected readonly doneWorkspace: import("@angular/core").Signal<Workspace | undefined>;
2344
+ /** Two workspaces with the same name cannot be told apart in a list: the name is not offered twice (whatever its case). */
2345
+ protected readonly duplicate: import("@angular/core").Signal<boolean>;
2346
+ protected readonly canCreate: import("@angular/core").Signal<boolean>;
2347
+ private timer;
2348
+ constructor();
2349
+ protected load(): Promise<void>;
2350
+ protected isNew(workspace: Workspace): boolean;
2351
+ protected add(workspace: Workspace): Promise<void>;
2352
+ protected onNameKeydown(event: KeyboardEvent): void;
2353
+ protected cancelNew(): void;
2354
+ protected create(): Promise<void>;
2355
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspacePicker, never>;
2356
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspacePicker, "workspace-picker", never, {
2357
+ "mode": {
2358
+ "alias": "mode";
2359
+ "required": false;
2360
+ "isSignal": true;
2361
+ };
2362
+ "documents": {
2363
+ "alias": "documents";
2364
+ "required": false;
2365
+ "isSignal": true;
2366
+ };
2367
+ "fromWorkspaceId": {
2368
+ "alias": "fromWorkspaceId";
2369
+ "required": false;
2370
+ "isSignal": true;
2371
+ };
2372
+ "documentIds": {
2373
+ "alias": "documentIds";
2374
+ "required": false;
2375
+ "isSignal": true;
2376
+ };
2377
+ }, {
2378
+ "completed": "completed";
2379
+ }, never, never, true, never>;
2380
+ }
2381
+ /**
2382
+ * Opens the "Save to / Move to workspace" picker from a host page's own button, and
2383
+ * says what came of it. It draws nothing itself: it is what the `lexiq-workspace-picker`
2384
+ * custom element wraps, for hosts that have no Angular component to open the dialog from.
2385
+ * In Angular, call `PickWorkspace.call(...)` directly.
2386
+ */
2387
+ export declare class WorkspacePickerLauncher {
2388
+ /** Documents were saved to a workspace: its name. */
2389
+ readonly saved: import("@angular/core").OutputEmitterRef<string>;
2390
+ /** Documents were moved to another workspace. */
2391
+ readonly moved: import("@angular/core").OutputEmitterRef<void>;
2392
+ private readonly injector;
2393
+ /** Opens "Save to workspace" under `anchor`. */
2394
+ openSaveCard(anchor: HTMLElement, options?: {
2395
+ documents?: readonly PickerDocument[];
2396
+ }): Promise<void>;
2397
+ /** Opens "Move to workspace" under `anchor`, for documents of the workspace they come from. */
2398
+ openMoveMenu(anchor: HTMLElement, documentIds: readonly string[], options: {
2399
+ fromWorkspaceId: string;
2400
+ }): Promise<void>;
2401
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspacePickerLauncher, never>;
2402
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspacePickerLauncher, "workspace-picker-launcher", never, {}, {
2403
+ "saved": "saved";
2404
+ "moved": "moved";
2405
+ }, never, never, true, never>;
2406
+ }
2407
+ interface WorkspacePickerDialogConfig {
2408
+ mode: "save" | "move";
2409
+ /** Where the popover opens from. Without one (or on a narrow window) it is a sheet. */
2410
+ anchor?: HTMLElement | null;
2411
+ /** `save`: the documents to attach, by name. */
2412
+ documents?: readonly PickerDocument[];
2413
+ /** `move`: where they come from, and which of its documents. */
2414
+ fromWorkspaceId?: string | null;
2415
+ documentIds?: readonly string[];
2416
+ }
2417
+ /** What the dialog resolves with once the documents are in: the workspace they went to. */
2418
+ type WorkspacePickerResult = {
2419
+ type: "dialog-confirm";
2420
+ workspace: Workspace;
2421
+ };
2422
+ /**
2423
+ * "Save to workspace" / "Move to workspace", as a popover under the button that asked
2424
+ * (`anchor`), or a sheet at the bottom of a narrow screen.
2425
+ *
2426
+ * ```ts
2427
+ * const result = await PickWorkspace.call({ mode: "save", anchor: button, documents: [{ name: "report.pdf" }] });
2428
+ * ```
2429
+ *
2430
+ * The popover is galactik's `[popover]` — the browser's light dismiss closes it on an outside
2431
+ * press or Escape, and floating-ui keeps it under the anchor; the sheet is galactik's `Sheet`,
2432
+ * modal, with its backdrop. Which one is decided when it opens.
2433
+ */
2434
+ export declare class WorkspacePickerDialog {
2435
+ protected readonly call: import("@sinequa/galactik").CallRef<WorkspacePickerDialogConfig, DialogResult>;
2436
+ private readonly injector;
2437
+ protected readonly config: WorkspacePickerDialogConfig;
2438
+ protected readonly title: import("@angular/core").Signal<"documentWorkspaces.picker.moveToWorkspace" | "documentWorkspaces.picker.saveToWorkspace">;
2439
+ /** A popover when there is an anchor to hang it from and room for it; a sheet otherwise. Decided once, when it opens. */
2440
+ protected readonly asPopover: boolean;
2441
+ protected readonly popoverId: string;
2442
+ private readonly popover;
2443
+ private readonly sheet;
2444
+ private outcome;
2445
+ private ended;
2446
+ constructor();
2447
+ /**
2448
+ * The popover finds its trigger by `popovertarget`, so the host's button gets it for as long as the
2449
+ * popover is being placed. The native behaviour of such an attribute — toggling the popover on the
2450
+ * button's own click — is not wanted: what a press on the button means is the host's business.
2451
+ */
2452
+ private showPopover;
2453
+ protected complete(workspace: Workspace): void;
2454
+ /** The popover is closed — by the picker, an outside press, Escape: that is the end of the call. */
2455
+ protected onToggle(event: Event): void;
2456
+ protected onSheetClosed(event: DialogResult): void;
2457
+ private end;
2458
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspacePickerDialog, never>;
2459
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspacePickerDialog, "workspace-picker-dialog", never, {}, {}, never, never, true, never>;
2460
+ }
2461
+ /** Imperative entry point: `await PickWorkspace.call({ mode: "save", anchor, documents })`. */
2462
+ export declare const PickWorkspace: import("@sinequa/galactik").Callable<WorkspacePickerDialogConfig, DialogResult>;
2463
+ /**
2464
+ * The whole workspaces view in one component: the list, a workspace with its documents
2465
+ * and preview, the dialogs they open, and the upload tracker.
2466
+ *
2467
+ * It is the piece that decides where things go — which the parts leave to their host:
2468
+ * choosing a workspace opens it, "Add workspace" opens the dialog and then the new
2469
+ * workspace, leaving a workspace goes back to the list. `workspaceId` is what is shown
2470
+ * (`null` is the list), two-way: a host can open a workspace, and is told which is open.
2471
+ *
2472
+ * ```html
2473
+ * <workspaces-shell uploadEndpoint="/tus" [(workspaceId)]="id" (askFollowUp)="chat.ask($event)" />
2474
+ * ```
2475
+ *
2476
+ * The parts are exported as well (`WorkspaceList`, `WorkspaceDetail`…) for a host that
2477
+ * wants its own layout. The shell is also what the `lexiq-workspaces` custom element
2478
+ * wraps, for pages that are not Angular.
2479
+ */
2480
+ export declare class WorkspacesShell {
2481
+ /** The workspace shown; `null` for the list. */
2482
+ readonly workspaceId: import("@angular/core").ModelSignal<string | null>;
2483
+ /** The tus endpoint uploads go to. */
2484
+ readonly uploadEndpoint: import("@angular/core").InputSignal<string>;
2485
+ /** The query the preview web service renders a document for. */
2486
+ readonly queryName: import("@angular/core").InputSignal<string>;
2487
+ /** `dark` turns the dark tokens on for the shell; otherwise it follows the page. */
2488
+ readonly theme: import("@angular/core").InputSignal<"light" | "dark" | null>;
2489
+ /** A workspace was created from here. */
2490
+ readonly workspaceCreated: import("@angular/core").OutputEmitterRef<Workspace>;
2491
+ /** A workspace was edited from here. */
2492
+ readonly workspaceUpdated: import("@angular/core").OutputEmitterRef<Workspace>;
2493
+ /** "Ask follow-up" on a previewed document. */
2494
+ readonly askFollowUp: import("@angular/core").OutputEmitterRef<AskFollowUp>;
2495
+ private readonly injector;
2496
+ /** Opens the "Add workspace" dialog, then the workspace it made. */
2497
+ addWorkspace(): Promise<void>;
2498
+ static ɵfac: i0.ɵɵFactoryDeclaration<WorkspacesShell, never>;
2499
+ static ɵcmp: i0.ɵɵComponentDeclaration<WorkspacesShell, "workspaces-shell", never, {
2500
+ "workspaceId": {
2501
+ "alias": "workspaceId";
2502
+ "required": false;
2503
+ "isSignal": true;
2504
+ };
2505
+ "uploadEndpoint": {
2506
+ "alias": "uploadEndpoint";
2507
+ "required": false;
2508
+ "isSignal": true;
2509
+ };
2510
+ "queryName": {
2511
+ "alias": "queryName";
2512
+ "required": false;
2513
+ "isSignal": true;
2514
+ };
2515
+ "theme": {
2516
+ "alias": "theme";
2517
+ "required": false;
2518
+ "isSignal": true;
2519
+ };
2520
+ }, {
2521
+ "workspaceId": "workspaceIdChange";
2522
+ "workspaceCreated": "workspaceCreated";
2523
+ "workspaceUpdated": "workspaceUpdated";
2524
+ "askFollowUp": "askFollowUp";
2525
+ }, never, never, true, never>;
2526
+ }
2527
+ export type { AddFilesOptions, AskFollowUp, AssignableRole, ConfirmDialogConfig, ConnectionNotice, CreateWorkspaceInput, CurrentUser, DocumentLabels, DocumentStatus, DocumentType, Member, MentionSelection, Pagination, PickedPrincipal, PickerDocument, PrincipalSummary, RemoveWorkspacesResult, ResolvedPrincipal, Selection, SelectionOptions, ShareDialogConfig, TextSearch, TextSearchOptions, TrackFileInput, TrackedUpload, TrackerWorkspace, UploadDialogConfig, UploadEvents, UploadFactory, UploadHandle, UploadOptions, UploadStatus, UploadTarget, UploadTrackerState, Workspace, WorkspaceColor, WorkspaceDocument, WorkspaceEditDialogConfig, WorkspaceEditResult, WorkspacePickerDialogConfig, WorkspacePickerResult, WorkspaceRole, WorkspaceTab, WorkspaceTabCounts, WorkspacesState };