@sinequa/atomic-angular 1.15.1 → 1.16.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.
|
@@ -211,7 +211,8 @@ declare const WorkspacesStore: _angular_core.Type<{
|
|
|
211
211
|
create: ({ name, description, tags, owners, readers }: CreateWorkspaceInput) => Promise<Workspace>;
|
|
212
212
|
update: (id: string, patch: UpdateContainerInput) => Promise<Workspace | null>;
|
|
213
213
|
remove: (ids: string[]) => Promise<RemoveWorkspacesResult>;
|
|
214
|
-
|
|
214
|
+
forget: (id: string) => void;
|
|
215
|
+
loadDocuments: (workspaceId: string, signal?: AbortSignal) => Promise<WorkspaceDocument[]>;
|
|
215
216
|
loadMoreDocuments: (workspaceId: string) => Promise<WorkspaceDocument[]>;
|
|
216
217
|
addDocuments: (workspaceId: string, documents: WorkspaceDocument[]) => void;
|
|
217
218
|
discardDocuments: (workspaceId: string, documentIds: string[]) => void;
|
|
@@ -868,7 +869,9 @@ type ViewMode$1 = "grid" | "list";
|
|
|
868
869
|
* Each time it is shown it refreshes: workspaces created by others since the last
|
|
869
870
|
* time appear, and the document counts match the server. The list already on
|
|
870
871
|
* screen stays until the answer lands, and a failure leaves it untouched behind
|
|
871
|
-
* an inline error.
|
|
872
|
+
* an inline error. While it stays shown, it keeps polling in the background
|
|
873
|
+
* (`injectLiveRefresh`) so a workspace created by someone else — one the current
|
|
874
|
+
* user only reads, say — appears on its own, with no action needed.
|
|
872
875
|
*/
|
|
873
876
|
declare class WorkspaceList {
|
|
874
877
|
/** A workspace was chosen for opening. */
|
|
@@ -897,7 +900,8 @@ declare class WorkspaceList {
|
|
|
897
900
|
create: ({ name, description, tags, owners, readers }: _sinequa_atomic_angular_document_workspaces.CreateWorkspaceInput) => Promise<Workspace>;
|
|
898
901
|
update: (id: string, patch: _sinequa_atomic.UpdateContainerInput) => Promise<Workspace | null>;
|
|
899
902
|
remove: (ids: string[]) => Promise<_sinequa_atomic_angular_document_workspaces.RemoveWorkspacesResult>;
|
|
900
|
-
|
|
903
|
+
forget: (id: string) => void;
|
|
904
|
+
loadDocuments: (workspaceId: string, signal?: AbortSignal) => Promise<_sinequa_atomic_angular_document_workspaces.WorkspaceDocument[]>;
|
|
901
905
|
loadMoreDocuments: (workspaceId: string) => Promise<_sinequa_atomic_angular_document_workspaces.WorkspaceDocument[]>;
|
|
902
906
|
addDocuments: (workspaceId: string, documents: _sinequa_atomic_angular_document_workspaces.WorkspaceDocument[]) => void;
|
|
903
907
|
discardDocuments: (workspaceId: string, documentIds: string[]) => void;
|
|
@@ -936,6 +940,7 @@ declare class WorkspaceList {
|
|
|
936
940
|
clearQuery: () => void;
|
|
937
941
|
};
|
|
938
942
|
protected readonly selection: _sinequa_atomic_angular_document_workspaces.Selection<Workspace>;
|
|
943
|
+
private readonly liveRefresh;
|
|
939
944
|
constructor();
|
|
940
945
|
/** Shows what is cached at once and refreshes behind it, or loads for the first time. */
|
|
941
946
|
protected load(): Promise<void>;
|
|
@@ -1123,6 +1128,9 @@ type ViewMode = "grid" | "list";
|
|
|
1123
1128
|
* shown: it says what the user asked for (`back`, `deleted`, `askFollowUp`) and whoever
|
|
1124
1129
|
* hosts it decides. The document open in the preview is the `previewedDocumentId` model.
|
|
1125
1130
|
*
|
|
1131
|
+
* While a workspace stays open, it keeps polling its documents in the background
|
|
1132
|
+
* (`injectLiveRefresh`), so a document another user adds to it shows up on its own.
|
|
1133
|
+
*
|
|
1126
1134
|
* Only someone who can write sees the edit, share, delete, upload and select controls.
|
|
1127
1135
|
*/
|
|
1128
1136
|
declare class WorkspaceDetail {
|
|
@@ -1153,6 +1161,8 @@ declare class WorkspaceDetail {
|
|
|
1153
1161
|
protected readonly refreshing: _angular_core.WritableSignal<boolean>;
|
|
1154
1162
|
protected readonly loadingMore: _angular_core.WritableSignal<boolean>;
|
|
1155
1163
|
protected readonly loadMoreError: _angular_core.WritableSignal<string | null>;
|
|
1164
|
+
/** The background refresh found the workspace gone from the server (deleted elsewhere). */
|
|
1165
|
+
protected readonly deletedElsewhere: _angular_core.WritableSignal<boolean>;
|
|
1156
1166
|
protected readonly workspace: _angular_core.Signal<Workspace | undefined>;
|
|
1157
1167
|
protected readonly documents: _angular_core.Signal<WorkspaceDocument[]>;
|
|
1158
1168
|
protected readonly previewed: _angular_core.Signal<WorkspaceDocument | null>;
|
|
@@ -1167,8 +1177,15 @@ declare class WorkspaceDetail {
|
|
|
1167
1177
|
protected readonly filtered: _angular_core.Signal<WorkspaceDocument[]>;
|
|
1168
1178
|
private readonly writable;
|
|
1169
1179
|
protected readonly selection: _sinequa_atomic_angular_document_workspaces.Selection<WorkspaceDocument>;
|
|
1180
|
+
private readonly liveRefresh;
|
|
1170
1181
|
constructor();
|
|
1171
1182
|
private open;
|
|
1183
|
+
/**
|
|
1184
|
+
* Leaving from here: once the background refresh found the workspace gone,
|
|
1185
|
+
* the cache still holds it (so its name kept showing) — drop it now, so a
|
|
1186
|
+
* stale card does not show it back on the list.
|
|
1187
|
+
*/
|
|
1188
|
+
protected leave(): void;
|
|
1172
1189
|
/** Reads the workspace (when it is not cached) and its documents, for the workspace currently shown. */
|
|
1173
1190
|
protected load(): Promise<void>;
|
|
1174
1191
|
/** Reads the documents again without leaving the view; a failure leaves the ones shown as they are. */
|
|
@@ -7,7 +7,7 @@ import { Upload } from 'tus-js-client';
|
|
|
7
7
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
8
8
|
import { TranslocoService, TranslocoPipe, provideTranslocoScope } from '@jsverse/transloco';
|
|
9
9
|
import { Temporal } from 'temporal-polyfill';
|
|
10
|
-
import { DialogComponent, ButtonComponent, DialogContentComponent, DialogFooterComponent, DialogTitleComponent, FolderIcon, UserIcon, UserRoundIcon, FileIcon, TooltipDirective, TrashIcon, DialogService, ListIcon, MagnifyingGlassIcon, PlusIcon, XMarkIcon, FileSpreadsheetIcon, FileTextIcon, UploadIcon, DialogHeaderComponent, ChatPlusIcon, ChevronDownIcon, ChevronUpIcon, ExternalLinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, EyeIcon, KeyIcon, MenuComponent, MenuContentComponent, MenuItemComponent, PenToSquareIcon, RefreshIcon, TagIcon, CheckIcon, CircleXIcon, SyncIcon } from '@sinequa/ui';
|
|
10
|
+
import { DialogComponent, ButtonComponent, DialogContentComponent, DialogFooterComponent, DialogTitleComponent, FolderIcon, UserIcon, UserRoundIcon, FileIcon, TooltipDirective, TrashIcon, DialogService, ListIcon, MagnifyingGlassIcon, PlusIcon, XMarkIcon, FileSpreadsheetIcon, FileTextIcon, UploadIcon, FileSearchIcon, DialogHeaderComponent, ChatPlusIcon, ChevronDownIcon, ChevronUpIcon, ExternalLinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, EyeIcon, KeyIcon, MenuComponent, MenuContentComponent, MenuItemComponent, PenToSquareIcon, RefreshIcon, TagIcon, CheckIcon, CircleXIcon, SyncIcon } from '@sinequa/ui';
|
|
11
11
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
12
12
|
import { computePosition, offset, flip, shift, autoUpdate } from '@floating-ui/dom';
|
|
13
13
|
|
|
@@ -164,8 +164,8 @@ const WorkspacesStore = signalStore({ providedIn: "root" }, withDevtools("Worksp
|
|
|
164
164
|
};
|
|
165
165
|
});
|
|
166
166
|
};
|
|
167
|
-
const loadDocuments = async (workspaceId) => {
|
|
168
|
-
const res = await listContainerDocuments(workspaceId, { page: 1, pageSize: PAGE_SIZE });
|
|
167
|
+
const loadDocuments = async (workspaceId, signal) => {
|
|
168
|
+
const res = await listContainerDocuments(workspaceId, { page: 1, pageSize: PAGE_SIZE }, { signal });
|
|
169
169
|
const documents = res.documents.map(mapContainerDocumentToWorkspaceDocument);
|
|
170
170
|
patchState(store, state => ({
|
|
171
171
|
documentPagination: { ...state.documentPagination, [workspaceId]: { page: res.page, total: res.total } }
|
|
@@ -314,6 +314,23 @@ const WorkspacesStore = signalStore({ providedIn: "root" }, withDevtools("Worksp
|
|
|
314
314
|
}));
|
|
315
315
|
return { succeeded: result.succeeded, failed: result.failed.map(f => ({ id: f.containerId, error: f.error })) };
|
|
316
316
|
},
|
|
317
|
+
/**
|
|
318
|
+
* Local only — for when a read already proved a workspace is gone from the
|
|
319
|
+
* server (a 404), rather than this session deleting it: the detail view
|
|
320
|
+
* calls this once the user actually leaves a workspace its background
|
|
321
|
+
* refresh found deleted, instead of {@link remove}, which would call
|
|
322
|
+
* DELETE on something already deleted. Kept in the cache until then, so
|
|
323
|
+
* the view can still show its name while it explains what happened.
|
|
324
|
+
*/
|
|
325
|
+
forget(id) {
|
|
326
|
+
patchState(store, state => ({
|
|
327
|
+
workspaces: state.workspaces.filter(w => w.id !== id),
|
|
328
|
+
pagination: state.workspaces.some(w => w.id === id) ? { ...state.pagination, total: Math.max(0, state.pagination.total - 1) } : state.pagination,
|
|
329
|
+
documents: Object.fromEntries(Object.entries(state.documents).filter(([wid]) => wid !== id)),
|
|
330
|
+
documentPagination: Object.fromEntries(Object.entries(state.documentPagination).filter(([wid]) => wid !== id)),
|
|
331
|
+
selectedIds: state.selectedIds.filter(selected => selected !== id)
|
|
332
|
+
}));
|
|
333
|
+
},
|
|
317
334
|
loadDocuments,
|
|
318
335
|
/** Without this, a workspace with more than one page of documents is silently truncated. */
|
|
319
336
|
async loadMoreDocuments(workspaceId) {
|
|
@@ -2314,6 +2331,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
2314
2331
|
args: [{ name: "highlightMatch" }]
|
|
2315
2332
|
}] });
|
|
2316
2333
|
|
|
2334
|
+
/**
|
|
2335
|
+
* Keeps a view fresh while it stays open, without the user ever asking: `tick`
|
|
2336
|
+
* re-fetches whatever the view shows and reports whether the result differs
|
|
2337
|
+
* from before — the loop speeds back up on a change and backs off while idle,
|
|
2338
|
+
* exactly like {@link StatusPoller} already does for upload status.
|
|
2339
|
+
*
|
|
2340
|
+
* Paused while the browser reports no network (resumed the moment it is back),
|
|
2341
|
+
* and fully stopped once the injecting component is destroyed. The very first
|
|
2342
|
+
* scheduled tick is a no-op: whatever calls `start()` has normally just
|
|
2343
|
+
* fetched on its own, so re-fetching the same thing instantly would be
|
|
2344
|
+
* redundant — the first real poll happens one `intervalMs` later.
|
|
2345
|
+
*
|
|
2346
|
+
* Must be called from an injection context.
|
|
2347
|
+
*/
|
|
2348
|
+
function injectLiveRefresh(tick, options = {}) {
|
|
2349
|
+
assertInInjectionContext(injectLiveRefresh);
|
|
2350
|
+
let shouldRun = false;
|
|
2351
|
+
let first = true;
|
|
2352
|
+
const poller = new StatusPoller({
|
|
2353
|
+
intervalMs: options.intervalMs,
|
|
2354
|
+
maxIntervalMs: options.maxIntervalMs,
|
|
2355
|
+
tick: async (signal) => {
|
|
2356
|
+
if (first) {
|
|
2357
|
+
first = false;
|
|
2358
|
+
return { pending: true, changed: true };
|
|
2359
|
+
}
|
|
2360
|
+
return { pending: true, changed: await tick(signal) };
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
if (typeof window !== "undefined") {
|
|
2364
|
+
const resume = () => {
|
|
2365
|
+
if (shouldRun)
|
|
2366
|
+
poller.start();
|
|
2367
|
+
};
|
|
2368
|
+
const pause = () => poller.stop();
|
|
2369
|
+
window.addEventListener("online", resume);
|
|
2370
|
+
window.addEventListener("offline", pause);
|
|
2371
|
+
inject(DestroyRef).onDestroy(() => {
|
|
2372
|
+
window.removeEventListener("online", resume);
|
|
2373
|
+
window.removeEventListener("offline", pause);
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
inject(DestroyRef).onDestroy(() => poller.stop());
|
|
2377
|
+
return {
|
|
2378
|
+
start() {
|
|
2379
|
+
shouldRun = true;
|
|
2380
|
+
if (typeof navigator === "undefined" || navigator.onLine)
|
|
2381
|
+
poller.start();
|
|
2382
|
+
},
|
|
2383
|
+
stop() {
|
|
2384
|
+
shouldRun = false;
|
|
2385
|
+
poller.stop();
|
|
2386
|
+
}
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2317
2390
|
/**
|
|
2318
2391
|
* Maps a caught error to the message shown in an inline error banner.
|
|
2319
2392
|
*
|
|
@@ -3063,7 +3136,9 @@ const TABS = [
|
|
|
3063
3136
|
* Each time it is shown it refreshes: workspaces created by others since the last
|
|
3064
3137
|
* time appear, and the document counts match the server. The list already on
|
|
3065
3138
|
* screen stays until the answer lands, and a failure leaves it untouched behind
|
|
3066
|
-
* an inline error.
|
|
3139
|
+
* an inline error. While it stays shown, it keeps polling in the background
|
|
3140
|
+
* (`injectLiveRefresh`) so a workspace created by someone else — one the current
|
|
3141
|
+
* user only reads, say — appears on its own, with no action needed.
|
|
3067
3142
|
*/
|
|
3068
3143
|
class WorkspaceList {
|
|
3069
3144
|
/** A workspace was chosen for opening. */
|
|
@@ -3088,6 +3163,19 @@ class WorkspaceList {
|
|
|
3088
3163
|
key: (workspace) => workspace.id,
|
|
3089
3164
|
selectable: workspace => canWrite(workspace.currentUserRole)
|
|
3090
3165
|
});
|
|
3166
|
+
// Background polling, started once the list has something to compare against:
|
|
3167
|
+
// each tick re-reloads and compares the full result to what was cached before
|
|
3168
|
+
// it, not just the set of ids — a workspace renamed, re-tagged or whose access
|
|
3169
|
+
// changed keeps the same id, and would otherwise look unchanged forever, which
|
|
3170
|
+
// would both hide the edit and widen the backoff on ticks that are not actually idle.
|
|
3171
|
+
liveRefresh = injectLiveRefresh(async () => {
|
|
3172
|
+
const before = JSON.stringify(this.store.workspaces());
|
|
3173
|
+
await this.store.reload();
|
|
3174
|
+
const changed = JSON.stringify(this.store.workspaces()) !== before;
|
|
3175
|
+
if (changed)
|
|
3176
|
+
this.selection.exit(); // the ticks belonged to the list that was there before
|
|
3177
|
+
return changed;
|
|
3178
|
+
}, { intervalMs: 20_000, maxIntervalMs: 120_000 });
|
|
3091
3179
|
constructor() {
|
|
3092
3180
|
void this.load();
|
|
3093
3181
|
}
|
|
@@ -3097,12 +3185,14 @@ class WorkspaceList {
|
|
|
3097
3185
|
if (this.store.loaded()) {
|
|
3098
3186
|
this.status.set("ready");
|
|
3099
3187
|
await this.refresh();
|
|
3188
|
+
this.liveRefresh.start();
|
|
3100
3189
|
return;
|
|
3101
3190
|
}
|
|
3102
3191
|
this.status.set("loading");
|
|
3103
3192
|
try {
|
|
3104
3193
|
await this.store.ensureLoaded();
|
|
3105
3194
|
this.status.set("ready");
|
|
3195
|
+
this.liveRefresh.start();
|
|
3106
3196
|
}
|
|
3107
3197
|
catch (error) {
|
|
3108
3198
|
this.loadError.set(workspaceErrorMessage(error));
|
|
@@ -3889,6 +3979,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
3889
3979
|
}]
|
|
3890
3980
|
}], propDecorators: { document: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: true }] }], workspaceRole: [{ type: i0.Input, args: [{ isSignal: true, alias: "workspaceRole", required: true }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], selecting: [{ type: i0.Input, args: [{ isSignal: true, alias: "selecting", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], deleteRequested: [{ type: i0.Output, args: ["deleteRequested"] }], selectedChange: [{ type: i0.Output, args: ["selectedChange"] }] } });
|
|
3891
3981
|
|
|
3982
|
+
/**
|
|
3983
|
+
* The file-search glyph on a teal tile — the same tile `EmptyFolderIcon` uses
|
|
3984
|
+
* for an empty workspace or an empty list, for a workspace or document the
|
|
3985
|
+
* server no longer has.
|
|
3986
|
+
*/
|
|
3987
|
+
class NotFoundIcon {
|
|
3988
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: NotFoundIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3989
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.29", type: NotFoundIcon, isStandalone: true, selector: "not-found-icon", host: { attributes: { "data-testid": "not-found-icon" }, classAttribute: "mb-1 inline-flex size-14 shrink-0 items-center justify-center rounded-(--radius-xl) bg-(--teal-100,#E0F4F1) text-(--teal-400,#4EBFB0)" }, ngImport: i0, template: `<file-search-icon class="size-6" />`, isInline: true, dependencies: [{ kind: "component", type: FileSearchIcon, selector: "file-search-icon, FileSearchIcon, filesearchicon", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3990
|
+
}
|
|
3991
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: NotFoundIcon, decorators: [{
|
|
3992
|
+
type: Component,
|
|
3993
|
+
args: [{
|
|
3994
|
+
selector: "not-found-icon",
|
|
3995
|
+
imports: [FileSearchIcon],
|
|
3996
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3997
|
+
host: {
|
|
3998
|
+
class: "mb-1 inline-flex size-14 shrink-0 items-center justify-center rounded-(--radius-xl) bg-(--teal-100,#E0F4F1) text-(--teal-400,#4EBFB0)",
|
|
3999
|
+
"data-testid": "not-found-icon"
|
|
4000
|
+
},
|
|
4001
|
+
template: `<file-search-icon class="size-6" />`
|
|
4002
|
+
}]
|
|
4003
|
+
}] });
|
|
4004
|
+
|
|
3892
4005
|
/** The files staged for upload, before anything is sent. */
|
|
3893
4006
|
class UploadFileList {
|
|
3894
4007
|
files = input.required(...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
@@ -5643,6 +5756,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
5643
5756
|
* shown: it says what the user asked for (`back`, `deleted`, `askFollowUp`) and whoever
|
|
5644
5757
|
* hosts it decides. The document open in the preview is the `previewedDocumentId` model.
|
|
5645
5758
|
*
|
|
5759
|
+
* While a workspace stays open, it keeps polling its documents in the background
|
|
5760
|
+
* (`injectLiveRefresh`), so a document another user adds to it shows up on its own.
|
|
5761
|
+
*
|
|
5646
5762
|
* Only someone who can write sees the edit, share, delete, upload and select controls.
|
|
5647
5763
|
*/
|
|
5648
5764
|
class WorkspaceDetail {
|
|
@@ -5673,6 +5789,8 @@ class WorkspaceDetail {
|
|
|
5673
5789
|
refreshing = signal(false, ...(ngDevMode ? [{ debugName: "refreshing" }] : []));
|
|
5674
5790
|
loadingMore = signal(false, ...(ngDevMode ? [{ debugName: "loadingMore" }] : []));
|
|
5675
5791
|
loadMoreError = signal(null, ...(ngDevMode ? [{ debugName: "loadMoreError" }] : []));
|
|
5792
|
+
/** The background refresh found the workspace gone from the server (deleted elsewhere). */
|
|
5793
|
+
deletedElsewhere = signal(false, ...(ngDevMode ? [{ debugName: "deletedElsewhere" }] : []));
|
|
5676
5794
|
workspace = computed(() => this.store.workspace(this.workspaceId()), ...(ngDevMode ? [{ debugName: "workspace" }] : []));
|
|
5677
5795
|
// Only what is really uploaded or being indexed is listed: transfers and failures
|
|
5678
5796
|
// belong to the upload tracker, not to the workspace. A document with no status at
|
|
@@ -5698,6 +5816,42 @@ class WorkspaceDetail {
|
|
|
5698
5816
|
// Selecting only serves deleting, which a reader cannot do.
|
|
5699
5817
|
selectable: () => this.writable()
|
|
5700
5818
|
});
|
|
5819
|
+
// Background polling, re-scoped to whatever `workspaceId` is current on each
|
|
5820
|
+
// tick (no restart needed when the user switches workspaces): refreshes both
|
|
5821
|
+
// the workspace's own metadata (title, description, tags, owners/readers,
|
|
5822
|
+
// the current user's role) and its documents, so an edit or an access change
|
|
5823
|
+
// made elsewhere shows up here too, not just additions to the document list.
|
|
5824
|
+
// Comparing full snapshots (not just ids) catches an in-place edit that an
|
|
5825
|
+
// id-only diff would miss — a renamed workspace, a demoted role — and keeps
|
|
5826
|
+
// the backoff from widening on a tick that only looks unchanged by id.
|
|
5827
|
+
liveRefresh = injectLiveRefresh(async (signal) => {
|
|
5828
|
+
const id = this.workspaceId();
|
|
5829
|
+
const beforeWorkspace = JSON.stringify(this.store.workspace(id));
|
|
5830
|
+
const beforeDocuments = JSON.stringify(this.store.documentsOf(id));
|
|
5831
|
+
try {
|
|
5832
|
+
await Promise.all([this.store.fetch(id), this.store.loadDocuments(id, signal)]);
|
|
5833
|
+
}
|
|
5834
|
+
catch (error) {
|
|
5835
|
+
// Someone else deleted it: flag it and stop polling — there is nothing
|
|
5836
|
+
// left here to discover. The workspace stays in the shared cache (so its
|
|
5837
|
+
// name/description keep showing instead of the view going blank); `leave()`
|
|
5838
|
+
// drops it once the user actually goes back to the list.
|
|
5839
|
+
if (error instanceof ApiError && error.status === 404 && this.workspaceId() === id) {
|
|
5840
|
+
this.deletedElsewhere.set(true);
|
|
5841
|
+
this.selection.exit();
|
|
5842
|
+
this.liveRefresh.stop();
|
|
5843
|
+
return true;
|
|
5844
|
+
}
|
|
5845
|
+
throw error;
|
|
5846
|
+
}
|
|
5847
|
+
if (this.workspaceId() !== id)
|
|
5848
|
+
return false; // the user moved to another workspace meanwhile
|
|
5849
|
+
const documentsChanged = JSON.stringify(this.store.documentsOf(id)) !== beforeDocuments;
|
|
5850
|
+
const workspaceChanged = JSON.stringify(this.store.workspace(id)) !== beforeWorkspace;
|
|
5851
|
+
if (documentsChanged)
|
|
5852
|
+
this.uploads.watch(id); // a document that just appeared may still be indexing for someone else
|
|
5853
|
+
return documentsChanged || workspaceChanged;
|
|
5854
|
+
}, { intervalMs: 15_000, maxIntervalMs: 90_000 });
|
|
5701
5855
|
constructor() {
|
|
5702
5856
|
effect(() => {
|
|
5703
5857
|
this.workspaceId();
|
|
@@ -5723,8 +5877,19 @@ class WorkspaceDetail {
|
|
|
5723
5877
|
this.query.set("");
|
|
5724
5878
|
this.refreshError.set(null);
|
|
5725
5879
|
this.loadMoreError.set(null);
|
|
5880
|
+
this.deletedElsewhere.set(false);
|
|
5726
5881
|
await this.load();
|
|
5727
5882
|
}
|
|
5883
|
+
/**
|
|
5884
|
+
* Leaving from here: once the background refresh found the workspace gone,
|
|
5885
|
+
* the cache still holds it (so its name kept showing) — drop it now, so a
|
|
5886
|
+
* stale card does not show it back on the list.
|
|
5887
|
+
*/
|
|
5888
|
+
leave() {
|
|
5889
|
+
if (this.deletedElsewhere())
|
|
5890
|
+
this.store.forget(this.workspaceId());
|
|
5891
|
+
this.back.emit();
|
|
5892
|
+
}
|
|
5728
5893
|
/** Reads the workspace (when it is not cached) and its documents, for the workspace currently shown. */
|
|
5729
5894
|
async load() {
|
|
5730
5895
|
const id = this.workspaceId();
|
|
@@ -5747,6 +5912,7 @@ class WorkspaceDetail {
|
|
|
5747
5912
|
return;
|
|
5748
5913
|
this.status.set("ready");
|
|
5749
5914
|
this.uploads.watch(id);
|
|
5915
|
+
this.liveRefresh.start();
|
|
5750
5916
|
}
|
|
5751
5917
|
/** Reads the documents again without leaving the view; a failure leaves the ones shown as they are. */
|
|
5752
5918
|
async refresh() {
|
|
@@ -5846,7 +6012,7 @@ class WorkspaceDetail {
|
|
|
5846
6012
|
return "key" in message ? this.transloco.translate(message.key, message.params) : message.text;
|
|
5847
6013
|
}
|
|
5848
6014
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: WorkspaceDetail, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5849
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.29", type: WorkspaceDetail, isStandalone: true, selector: "workspace-detail", inputs: { workspaceId: { classPropertyName: "workspaceId", publicName: "workspaceId", isSignal: true, isRequired: true, transformFunction: null }, uploadEndpoint: { classPropertyName: "uploadEndpoint", publicName: "uploadEndpoint", isSignal: true, isRequired: false, transformFunction: null }, previewedDocumentId: { classPropertyName: "previewedDocumentId", publicName: "previewedDocumentId", isSignal: true, isRequired: false, transformFunction: null }, queryName: { classPropertyName: "queryName", publicName: "queryName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { previewedDocumentId: "previewedDocumentIdChange", back: "back", deleted: "deleted", workspaceUpdated: "workspaceUpdated", askFollowUp: "askFollowUp" }, host: { classAttribute: "flex h-full min-h-0" }, providers: [provideTranslocoScope("document-workspaces")], ngImport: i0, template: "\n <div class=\"@container flex min-h-0 min-w-0 flex-1 flex-col p-3 pb-2 md:p-8 md:pb-4\">\n <div class=\"mb-3 flex flex-wrap items-center gap-x-3 gap-y-2\">\n <button class=\"w-6\" size=\"sm\"\n variant=\"ghost\"\n [iconOnly]=\"true\"\n [attr.aria-label]=\"'documentWorkspaces.detail.back' | transloco\"\n [tooltip]=\"'documentWorkspaces.detail.back' | transloco\"\n (click)=\"back.emit()\">\n <chevron-down-icon class=\"rotate-90\" />\n </button>\n\n @if (workspace(); as workspace) {\n <h1 class=\"min-w-28 flex-[1_1_16rem] text-2xl leading-[31px] font-bold break-words text-(--font-neutral-black)\">{{ workspace.name }}</h1>\n\n <!-- Beside the title while they fit next to its 16rem, on a line of their own under it once they do not. -->\n <div class=\"ml-auto flex shrink-0 items-center gap-1\">\n <button size=\"sm\"\n variant=\"ghost\"\n class=\"gap-1.5 text-(--font-error-base) hover:bg-(--bg-error-lightest)\"\n [writerOnly]=\"workspace.currentUserRole\"\n (click)=\"confirmDeleteWorkspace(workspace)\">\n <trash-icon />\n {{ \"documentWorkspaces.common.delete\" | transloco }}\n </button>\n <button size=\"sm\" variant=\"ghost\" class=\"gap-1.5\" [writerOnly]=\"workspace.currentUserRole\" (click)=\"share(workspace)\">\n <user-round-icon />\n {{ \"documentWorkspaces.share.title\" | transloco }}\n </button>\n <button size=\"sm\" variant=\"ghost\" class=\"gap-1.5\" [writerOnly]=\"workspace.currentUserRole\" (click)=\"edit(workspace)\">\n <pen-to-square-icon />\n {{ \"documentWorkspaces.common.edit\" | transloco }}\n </button>\n <button size=\"sm\"\n variant=\"ghost\"\n class=\"w-6\"\n [iconOnly]=\"true\"\n [disabled]=\"refreshing()\"\n [attr.aria-label]=\"'documentWorkspaces.common.refresh' | transloco\"\n [tooltip]=\"'documentWorkspaces.common.refresh' | transloco\"\n (click)=\"refresh()\">\n <refresh-icon />\n </button>\n </div>\n }\n </div>\n\n @if (workspace(); as workspace) {\n @if (workspace.description) {\n <p class=\"mb-3 text-base text-(--font-primary-base)\">{{ workspace.description }}</p>\n }\n @if (workspace.tags.length) {\n <p class=\"mb-6 flex items-center gap-2 text-sm text-(--font-primary-base)\" data-testid=\"tags\">\n <tag-icon class=\"size-4\" />\n {{ workspace.tags.join(\", \") }}\n </p>\n }\n\n <div class=\"mb-3 flex flex-wrap items-center gap-2\">\n <div role=\"group\" class=\"inline-flex gap-1 rounded-full bg-(--bg-primary-lighter) p-0.5\">\n <button\n variant=\"none\"\n class=\"inline-flex size-6 items-center justify-center rounded-full text-(--font-primary-base) aria-pressed:bg-(--bg-neutral-white) aria-pressed:shadow-md\"\n [attr.aria-pressed]=\"viewMode() === 'grid'\"\n [attr.aria-label]=\"'documentWorkspaces.common.gridView' | transloco\"\n [tooltip]=\"'documentWorkspaces.common.gridView' | transloco\"\n (click)=\"viewMode.set('grid')\">\n <grid-icon class=\"size-3.5\" />\n </button>\n <button\n variant=\"none\"\n class=\"inline-flex size-6 items-center justify-center rounded-full text-(--font-primary-base) aria-pressed:bg-(--bg-neutral-white) aria-pressed:shadow-md\"\n [attr.aria-pressed]=\"viewMode() === 'list'\"\n [attr.aria-label]=\"'documentWorkspaces.common.listView' | transloco\"\n [tooltip]=\"'documentWorkspaces.common.listView' | transloco\"\n (click)=\"viewMode.set('list')\">\n <list-icon class=\"size-3.5\" />\n </button>\n </div>\n\n <div\n role=\"search\"\n class=\"flex h-9 min-w-32 flex-[0_1_15rem] items-center gap-2 rounded-full border border-(--stroke-primary-light) bg-(--bg-neutral-white) px-3 focus-within:border-(--stroke-primary-base)\">\n <magnifying-glass-icon class=\"size-4 shrink-0 text-(--font-primary-muted)\" />\n <input\n type=\"text\"\n class=\"min-w-0 flex-1 bg-transparent text-xs text-(--font-neutral-black) outline-none placeholder:text-(--font-primary-muted)\"\n [value]=\"query()\"\n [attr.placeholder]=\"'documentWorkspaces.detail.searchPlaceholder' | transloco\"\n [attr.aria-label]=\"'documentWorkspaces.detail.searchPlaceholder' | transloco\"\n (input)=\"onSearch($event)\" />\n @if (query()) {\n <button variant=\"none\" type=\"button\" class=\"inline-flex\" [attr.aria-label]=\"'documentWorkspaces.common.clearSearch' | transloco\" (click)=\"query.set('')\">\n <x-mark-icon class=\"size-4\" />\n </button>\n }\n </div>\n\n <button class=\"gap-1.5\" size=\"sm\" variant=\"primary\" [writerOnly]=\"workspace.currentUserRole\" (click)=\"upload(workspace)\">\n <upload-icon />\n {{ \"documentWorkspaces.detail.upload\" | transloco }}\n </button>\n\n @if (selection.selectableItems().length) {\n @if (selection.active()) {\n <div class=\"ml-auto flex items-center gap-2 @max-xl:order-last @max-xl:ml-0 @max-xl:basis-full\" data-testid=\"selection-bar\">\n <button variant=\"ghost\" size=\"xs\" (click)=\"selection.toggleAll()\">\n {{ (selection.allSelected() ? \"documentWorkspaces.common.unselectAll\" : \"documentWorkspaces.common.selectAll\") | transloco }}\n </button>\n <button class=\"gap-1.5 text-(--font-error-base) hover:bg-(--bg-error-lightest)\" variant=\"ghost\" size=\"xs\" [disabled]=\"!selection.count()\" (click)=\"deleteSelected()\">\n <trash-icon />\n {{ \"documentWorkspaces.common.delete\" | transloco }}\n </button>\n </div>\n }\n <button variant=\"ghost\" size=\"xs\" class=\"@max-xl:ml-auto\" [class.ml-auto]=\"!selection.active()\" (click)=\"selection.toggleMode()\">\n {{ (selection.active() ? \"documentWorkspaces.common.cancel\" : \"documentWorkspaces.common.select\") | transloco }}\n </button>\n }\n </div>\n\n <p class=\"mb-6 text-sm text-(--font-primary-base)\" aria-live=\"polite\" data-testid=\"count\">\n @if (selection.active()) {\n {{ \"documentWorkspaces.detail.selectedDocumentCount\" | transloco: { count: selection.count() } }}\n } @else {\n {{ \"documentWorkspaces.detail.fileCount\" | transloco: { count: workspace.documentCount } }}\n }\n </p>\n }\n\n @if (refreshError(); as message) {\n <p role=\"alert\" class=\"text-(--font-error-base) text-sm\">{{ \"documentWorkspaces.detail.refreshError\" | transloco: { error: message } }}</p>\n }\n\n <!-- Only the documents scroll: the title, the toolbar and the count stay in place. -->\n <div class=\"min-h-0 flex-1 overflow-y-auto\">\n @switch (status()) {\n @case (\"loading\") {\n <p class=\"text-(--font-primary-muted) text-sm\">{{ \"documentWorkspaces.detail.docsLoading\" | transloco }}</p>\n }\n @case (\"error\") {\n <div class=\"flex items-center gap-3 text-sm\" data-testid=\"load-error\">\n <span class=\"text-(--font-error-base)\">\n @if (loadError(); as message) {\n <workspace-message [message]=\"message\" />\n } @else {\n {{ \"documentWorkspaces.detail.docsLoadError\" | transloco }}\n }\n </span>\n <button variant=\"secondary\" size=\"xs\" (click)=\"load()\">{{ \"documentWorkspaces.common.retry\" | transloco }}</button>\n </div>\n }\n @default {\n @if (workspace(); as workspace) {\n @if (!documents().length) {\n <div class=\"flex flex-col items-center gap-1 py-8 text-center\" data-testid=\"empty\">\n <empty-folder-icon />\n <h5 class=\"text-sm font-semibold text-(--font-primary-dark)\">{{ \"documentWorkspaces.detail.emptyTitle\" | transloco }}</h5>\n <p class=\"max-w-70 text-sm text-(--font-primary-base)\">{{ \"documentWorkspaces.detail.emptySubtitle\" | transloco }}</p>\n </div>\n } @else if (!filtered().length) {\n <p class=\"text-(--font-primary-muted) py-8 text-center text-sm\" data-testid=\"no-match\">{{ \"documentWorkspaces.detail.noMatch\" | transloco }}</p>\n } @else if (viewMode() === \"grid\") {\n <div class=\"grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] content-start gap-4 pb-8\">\n @for (document of filtered(); track document.id) {\n <document-card\n [document]=\"document\"\n [workspaceRole]=\"workspace.currentUserRole\"\n [query]=\"query()\"\n [active]=\"document.id === previewedDocumentId()\"\n [selecting]=\"selection.active()\"\n [selected]=\"selection.isSelected(document)\"\n [selectable]=\"selection.canSelect(document)\"\n (opened)=\"togglePreview(document)\"\n (deleteRequested)=\"confirmDeleteDocuments([document])\"\n (selectedChange)=\"selection.toggle(document, $event)\" />\n }\n </div>\n } @else {\n <div role=\"table\" class=\"flex flex-col gap-3 pb-8\">\n <div role=\"row\" [attr.data-selecting]=\"selection.active() ? '' : null\" class=\"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] py-1 text-xs font-semibold text-(--font-primary-muted)\">\n @if (selection.active()) {\n <div role=\"columnheader\">\n <input\n type=\"checkbox\"\n class=\"accent-(--bg-primary-base) size-4\"\n [checked]=\"selection.allSelected()\"\n [indeterminate]=\"selection.partiallySelected()\"\n [attr.aria-label]=\"'documentWorkspaces.common.selectAll' | transloco\"\n (change)=\"selection.toggleAll()\" />\n\n </div>\n }\n <div role=\"columnheader\">{{ \"documentWorkspaces.detail.columnName\" | transloco }}</div>\n <div role=\"columnheader\" class=\"@max-3xl:hidden\">{{ \"documentWorkspaces.detail.columnSource\" | transloco }}</div>\n <div role=\"columnheader\" class=\"@max-3xl:hidden\">{{ \"documentWorkspaces.detail.columnSize\" | transloco }}</div>\n <div role=\"columnheader\">{{ \"documentWorkspaces.detail.columnDate\" | transloco }}</div>\n <div role=\"columnheader\"></div>\n </div>\n @for (document of filtered(); track document.id) {\n <document-row\n [document]=\"document\"\n [workspaceRole]=\"workspace.currentUserRole\"\n [query]=\"query()\"\n [active]=\"document.id === previewedDocumentId()\"\n [selecting]=\"selection.active()\"\n [selected]=\"selection.isSelected(document)\"\n [selectable]=\"selection.canSelect(document)\"\n (opened)=\"togglePreview(document)\"\n (deleteRequested)=\"confirmDeleteDocuments([document])\"\n (selectedChange)=\"selection.toggle(document, $event)\" />\n }\n </div>\n }\n\n @if (hasMore()) {\n <div class=\"flex flex-col items-center gap-2\">\n @if (loadMoreError(); as message) {\n <p role=\"alert\" class=\"text-(--font-error-base) text-sm\">{{ \"documentWorkspaces.detail.loadMoreError\" | transloco: { error: message } }}</p>\n }\n <button variant=\"secondary\" size=\"xs\" [disabled]=\"loadingMore()\" (click)=\"loadMore()\">\n {{ \"documentWorkspaces.detail.loadMore\" | transloco }}\n </button>\n </div>\n }\n } @else {\n <p class=\"text-(--font-primary-muted) py-8 text-center text-sm\" data-testid=\"not-found\">{{ \"documentWorkspaces.errors.notFound\" | transloco }}</p>\n }\n }\n }\n </div>\n </div>\n\n <!-- The list gives up part of its width to the preview, and takes it back when the preview closes. -->\n <div\n #handle=\"resizeHandle\"\n class=\"group/handle flex w-3 shrink-0 cursor-col-resize items-center justify-center max-md:hidden\"\n [class.hidden]=\"!previewed()\"\n [resizeHandle]=\"previewWidth()\"\n (resizeHandleChange)=\"userPreviewWidth.set($event)\"\n [resizeMin]=\"280\"\n [resizeMax]=\"maxPreviewWidth()\"\n [attr.aria-label]=\"'documentWorkspaces.preview.resize' | transloco\">\n <span class=\"h-6 w-0.5 rounded-full bg-(--stroke-primary-light) opacity-0 group-hover/handle:opacity-100 group-focus-visible/handle:opacity-100 group-data-resizing/handle:opacity-100\"></span>\n </div>\n <aside\n class=\"sticky top-3 my-3 mr-3 flex h-[calc(100dvh-1.5rem)] max-h-[calc(100%-1.5rem)] shrink-0 self-start overflow-hidden rounded-(--radius-xl) bg-(--bg-neutral-white) transition-[width] duration-[350ms] ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none data-open:border data-open:border-(--stroke-primary-lighter) data-open:shadow-[0_4px_6px_-2px_rgba(45,57,58,0.05),0_10px_15px_-3px_rgba(45,57,58,0.1)] max-md:data-open:fixed max-md:data-open:inset-0 max-md:data-open:z-50 max-md:data-open:m-0 max-md:data-open:w-full! max-md:data-open:max-h-none max-md:data-open:rounded-none\"\n [class.transition-none]=\"handle.resizing()\"\n [style.width.px]=\"previewed() ? previewWidth() : 0\"\n [attr.data-open]=\"previewed() ? '' : null\"\n [attr.aria-hidden]=\"previewed() ? null : 'true'\"\n data-testid=\"preview\">\n @if (previewed(); as previewed) {\n <!-- Its own width, so the content does not reflow while the card opens. -->\n <document-preview-panel\n class=\"shrink-0 max-md:w-full!\"\n [style.width.px]=\"previewWidth()\"\n [document]=\"previewed\"\n [queryName]=\"queryName()\"\n (closed)=\"previewedDocumentId.set(null)\"\n (askFollowUp)=\"askFollowUp.emit($event)\" />\n }\n </aside>\n ", isInline: true, dependencies: [{ kind: "component", type: ChevronDownIcon, selector: "chevron-down, ChevronDown, chevrondown, chevron-down-icon, ChevronDownIcon, chevrondownicon", inputs: ["class"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: DocumentCard, selector: "document-card", inputs: ["document", "workspaceRole", "query", "active", "selecting", "selected", "selectable"], outputs: ["opened", "deleteRequested", "selectedChange"] }, { kind: "component", type: DocumentPreviewPanel, selector: "document-preview-panel", inputs: ["document", "queryName"], outputs: ["closed", "askFollowUp"] }, { kind: "component", type: DocumentRow, selector: "document-row", inputs: ["document", "workspaceRole", "query", "active", "selecting", "selected", "selectable"], outputs: ["opened", "deleteRequested", "selectedChange"] }, { kind: "component", type: EmptyFolderIcon, selector: "empty-folder-icon" }, { kind: "component", type: GridIcon, selector: "grid-icon", inputs: ["class"] }, { kind: "component", type: ListIcon, selector: "list-icon, ListIcon", inputs: ["class"] }, { kind: "component", type: MagnifyingGlassIcon, selector: "magnifying-glass-icon, MagnifyingGlassIcon", inputs: ["class"] }, { kind: "component", type: PenToSquareIcon, selector: "pen-to-square-icon, PenToSquareIcon", inputs: ["class"] }, { kind: "component", type: RefreshIcon, selector: "refresh-icon, RefreshIcon, refreshicon", inputs: ["class"] }, { kind: "directive", type: ResizeHandle, selector: "[resizeHandle]", inputs: ["resizeHandle", "resizeMin", "resizeMax", "resizeStep", "resizeEdge"], outputs: ["resizeHandleChange"], exportAs: ["resizeHandle"] }, { kind: "component", type: TagIcon, selector: "tag-icon, TagIcon", inputs: ["class"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip]", inputs: ["tooltip", "strategy", "tooltip-position", "tooltip-delay", "tooltip-duration", "tooltip-offset"] }, { kind: "component", type: TrashIcon, selector: "trash-icon, TrashIcon", inputs: ["class"] }, { kind: "component", type: UploadIcon, selector: "upload-icon, UploadIcon, uploadicon", inputs: ["class"] }, { kind: "component", type: UserRoundIcon, selector: "user-round, UserRound, userround, user-round-icon, UserRoundIcon, userroundicon", inputs: ["class"] }, { kind: "component", type: WorkspaceMessage, selector: "workspace-message", inputs: ["message"] }, { kind: "directive", type: WriterOnly, selector: "[writerOnly]", inputs: ["writerOnly"] }, { kind: "component", type: XMarkIcon, selector: "XMarkIcon, xmark-icon, x-mark-icon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6015
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.29", type: WorkspaceDetail, isStandalone: true, selector: "workspace-detail", inputs: { workspaceId: { classPropertyName: "workspaceId", publicName: "workspaceId", isSignal: true, isRequired: true, transformFunction: null }, uploadEndpoint: { classPropertyName: "uploadEndpoint", publicName: "uploadEndpoint", isSignal: true, isRequired: false, transformFunction: null }, previewedDocumentId: { classPropertyName: "previewedDocumentId", publicName: "previewedDocumentId", isSignal: true, isRequired: false, transformFunction: null }, queryName: { classPropertyName: "queryName", publicName: "queryName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { previewedDocumentId: "previewedDocumentIdChange", back: "back", deleted: "deleted", workspaceUpdated: "workspaceUpdated", askFollowUp: "askFollowUp" }, host: { classAttribute: "flex h-full min-h-0" }, providers: [provideTranslocoScope("document-workspaces")], ngImport: i0, template: "\n <div class=\"@container flex min-h-0 min-w-0 flex-1 flex-col p-3 pb-2 md:p-8 md:pb-4\">\n <div class=\"mb-3 flex flex-wrap items-center gap-x-3 gap-y-2\">\n <button class=\"w-6\" size=\"sm\"\n variant=\"ghost\"\n [iconOnly]=\"true\"\n [attr.aria-label]=\"'documentWorkspaces.detail.back' | transloco\"\n [tooltip]=\"'documentWorkspaces.detail.back' | transloco\"\n (click)=\"leave()\">\n <chevron-down-icon class=\"rotate-90\" />\n </button>\n\n @if (workspace(); as workspace) {\n <h1 class=\"min-w-28 flex-[1_1_16rem] text-2xl leading-[31px] font-bold break-words text-(--font-neutral-black)\">{{ workspace.name }}</h1>\n\n <!-- Beside the title while they fit next to its 16rem, on a line of their own under it once they do not. -->\n @if (!deletedElsewhere()) {\n <div class=\"ml-auto flex shrink-0 items-center gap-1\">\n <button size=\"sm\"\n variant=\"ghost\"\n class=\"gap-1.5 text-(--font-error-base) hover:bg-(--bg-error-lightest)\"\n [writerOnly]=\"workspace.currentUserRole\"\n (click)=\"confirmDeleteWorkspace(workspace)\">\n <trash-icon />\n {{ \"documentWorkspaces.common.delete\" | transloco }}\n </button>\n <button size=\"sm\" variant=\"ghost\" class=\"gap-1.5\" [writerOnly]=\"workspace.currentUserRole\" (click)=\"share(workspace)\">\n <user-round-icon />\n {{ \"documentWorkspaces.share.title\" | transloco }}\n </button>\n <button size=\"sm\" variant=\"ghost\" class=\"gap-1.5\" [writerOnly]=\"workspace.currentUserRole\" (click)=\"edit(workspace)\">\n <pen-to-square-icon />\n {{ \"documentWorkspaces.common.edit\" | transloco }}\n </button>\n <button size=\"sm\"\n variant=\"ghost\"\n class=\"w-6\"\n [iconOnly]=\"true\"\n [disabled]=\"refreshing()\"\n [attr.aria-label]=\"'documentWorkspaces.common.refresh' | transloco\"\n [tooltip]=\"'documentWorkspaces.common.refresh' | transloco\"\n (click)=\"refresh()\">\n <refresh-icon />\n </button>\n </div>\n }\n }\n </div>\n\n @if (workspace(); as workspace) {\n @if (workspace.description) {\n <p class=\"mb-3 text-base text-(--font-primary-base)\">{{ workspace.description }}</p>\n }\n @if (workspace.tags.length) {\n <p class=\"mb-6 flex items-center gap-2 text-sm text-(--font-primary-base)\" data-testid=\"tags\">\n <tag-icon class=\"size-4\" />\n {{ workspace.tags.join(\", \") }}\n </p>\n }\n\n @if (!deletedElsewhere()) {\n <div class=\"mb-3 flex flex-wrap items-center gap-2\">\n <div role=\"group\" class=\"inline-flex gap-1 rounded-full bg-(--bg-primary-lighter) p-0.5\">\n <button\n variant=\"none\"\n class=\"inline-flex size-6 items-center justify-center rounded-full text-(--font-primary-base) aria-pressed:bg-(--bg-neutral-white) aria-pressed:shadow-md\"\n [attr.aria-pressed]=\"viewMode() === 'grid'\"\n [attr.aria-label]=\"'documentWorkspaces.common.gridView' | transloco\"\n [tooltip]=\"'documentWorkspaces.common.gridView' | transloco\"\n (click)=\"viewMode.set('grid')\">\n <grid-icon class=\"size-3.5\" />\n </button>\n <button\n variant=\"none\"\n class=\"inline-flex size-6 items-center justify-center rounded-full text-(--font-primary-base) aria-pressed:bg-(--bg-neutral-white) aria-pressed:shadow-md\"\n [attr.aria-pressed]=\"viewMode() === 'list'\"\n [attr.aria-label]=\"'documentWorkspaces.common.listView' | transloco\"\n [tooltip]=\"'documentWorkspaces.common.listView' | transloco\"\n (click)=\"viewMode.set('list')\">\n <list-icon class=\"size-3.5\" />\n </button>\n </div>\n\n <div\n role=\"search\"\n class=\"flex h-9 min-w-32 flex-[0_1_15rem] items-center gap-2 rounded-full border border-(--stroke-primary-light) bg-(--bg-neutral-white) px-3 focus-within:border-(--stroke-primary-base)\">\n <magnifying-glass-icon class=\"size-4 shrink-0 text-(--font-primary-muted)\" />\n <input\n type=\"text\"\n class=\"min-w-0 flex-1 bg-transparent text-xs text-(--font-neutral-black) outline-none placeholder:text-(--font-primary-muted)\"\n [value]=\"query()\"\n [attr.placeholder]=\"'documentWorkspaces.detail.searchPlaceholder' | transloco\"\n [attr.aria-label]=\"'documentWorkspaces.detail.searchPlaceholder' | transloco\"\n (input)=\"onSearch($event)\" />\n @if (query()) {\n <button variant=\"none\" type=\"button\" class=\"inline-flex\" [attr.aria-label]=\"'documentWorkspaces.common.clearSearch' | transloco\" (click)=\"query.set('')\">\n <x-mark-icon class=\"size-4\" />\n </button>\n }\n </div>\n\n <button class=\"gap-1.5\" size=\"sm\" variant=\"primary\" [writerOnly]=\"workspace.currentUserRole\" (click)=\"upload(workspace)\">\n <upload-icon />\n {{ \"documentWorkspaces.detail.upload\" | transloco }}\n </button>\n\n @if (selection.selectableItems().length) {\n @if (selection.active()) {\n <div class=\"ml-auto flex items-center gap-2 @max-xl:order-last @max-xl:ml-0 @max-xl:basis-full\" data-testid=\"selection-bar\">\n <button variant=\"ghost\" size=\"xs\" (click)=\"selection.toggleAll()\">\n {{ (selection.allSelected() ? \"documentWorkspaces.common.unselectAll\" : \"documentWorkspaces.common.selectAll\") | transloco }}\n </button>\n <button class=\"gap-1.5 text-(--font-error-base) hover:bg-(--bg-error-lightest)\" variant=\"ghost\" size=\"xs\" [disabled]=\"!selection.count()\" (click)=\"deleteSelected()\">\n <trash-icon />\n {{ \"documentWorkspaces.common.delete\" | transloco }}\n </button>\n </div>\n }\n <button variant=\"ghost\" size=\"xs\" class=\"@max-xl:ml-auto\" [class.ml-auto]=\"!selection.active()\" (click)=\"selection.toggleMode()\">\n {{ (selection.active() ? \"documentWorkspaces.common.cancel\" : \"documentWorkspaces.common.select\") | transloco }}\n </button>\n }\n </div>\n\n <p class=\"mb-6 text-sm text-(--font-primary-base)\" aria-live=\"polite\" data-testid=\"count\">\n @if (selection.active()) {\n {{ \"documentWorkspaces.detail.selectedDocumentCount\" | transloco: { count: selection.count() } }}\n } @else {\n {{ \"documentWorkspaces.detail.fileCount\" | transloco: { count: workspace.documentCount } }}\n }\n </p>\n }\n }\n\n @if (refreshError(); as message) {\n <p role=\"alert\" class=\"text-(--font-error-base) text-sm\">{{ \"documentWorkspaces.detail.refreshError\" | transloco: { error: message } }}</p>\n }\n\n <!-- Only the documents scroll: the title, the toolbar and the count stay in place. -->\n <div class=\"min-h-0 flex-1 overflow-y-auto\">\n @switch (status()) {\n @case (\"loading\") {\n <p class=\"text-(--font-primary-muted) text-sm\">{{ \"documentWorkspaces.detail.docsLoading\" | transloco }}</p>\n }\n @case (\"error\") {\n <div class=\"flex items-center gap-3 text-sm\" data-testid=\"load-error\">\n <span class=\"text-(--font-error-base)\">\n @if (loadError(); as message) {\n <workspace-message [message]=\"message\" />\n } @else {\n {{ \"documentWorkspaces.detail.docsLoadError\" | transloco }}\n }\n </span>\n <button variant=\"secondary\" size=\"xs\" (click)=\"load()\">{{ \"documentWorkspaces.common.retry\" | transloco }}</button>\n </div>\n }\n @default {\n @if (deletedElsewhere()) {\n <div class=\"flex flex-col items-center gap-1 py-8 text-center\" data-testid=\"not-found\">\n <not-found-icon />\n <p class=\"max-w-70 text-sm text-(--font-primary-base)\">{{ \"documentWorkspaces.errors.notFound\" | transloco }}</p>\n <button variant=\"secondary\" size=\"sm\" class=\"mt-2\" (click)=\"leave()\">{{ \"documentWorkspaces.detail.back\" | transloco }}</button>\n </div>\n } @else if (workspace(); as workspace) {\n @if (!documents().length) {\n <div class=\"flex flex-col items-center gap-1 py-8 text-center\" data-testid=\"empty\">\n <empty-folder-icon />\n <h5 class=\"text-sm font-semibold text-(--font-primary-dark)\">{{ \"documentWorkspaces.detail.emptyTitle\" | transloco }}</h5>\n <p class=\"max-w-70 text-sm text-(--font-primary-base)\">{{ \"documentWorkspaces.detail.emptySubtitle\" | transloco }}</p>\n </div>\n } @else if (!filtered().length) {\n <p class=\"text-(--font-primary-muted) py-8 text-center text-sm\" data-testid=\"no-match\">{{ \"documentWorkspaces.detail.noMatch\" | transloco }}</p>\n } @else if (viewMode() === \"grid\") {\n <div class=\"grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] content-start gap-4 pb-8\">\n @for (document of filtered(); track document.id) {\n <document-card\n [document]=\"document\"\n [workspaceRole]=\"workspace.currentUserRole\"\n [query]=\"query()\"\n [active]=\"document.id === previewedDocumentId()\"\n [selecting]=\"selection.active()\"\n [selected]=\"selection.isSelected(document)\"\n [selectable]=\"selection.canSelect(document)\"\n (opened)=\"togglePreview(document)\"\n (deleteRequested)=\"confirmDeleteDocuments([document])\"\n (selectedChange)=\"selection.toggle(document, $event)\" />\n }\n </div>\n } @else {\n <div role=\"table\" class=\"flex flex-col gap-3 pb-8\">\n <div role=\"row\" [attr.data-selecting]=\"selection.active() ? '' : null\" class=\"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] py-1 text-xs font-semibold text-(--font-primary-muted)\">\n @if (selection.active()) {\n <div role=\"columnheader\">\n <input\n type=\"checkbox\"\n class=\"accent-(--bg-primary-base) size-4\"\n [checked]=\"selection.allSelected()\"\n [indeterminate]=\"selection.partiallySelected()\"\n [attr.aria-label]=\"'documentWorkspaces.common.selectAll' | transloco\"\n (change)=\"selection.toggleAll()\" />\n\n </div>\n }\n <div role=\"columnheader\">{{ \"documentWorkspaces.detail.columnName\" | transloco }}</div>\n <div role=\"columnheader\" class=\"@max-3xl:hidden\">{{ \"documentWorkspaces.detail.columnSource\" | transloco }}</div>\n <div role=\"columnheader\" class=\"@max-3xl:hidden\">{{ \"documentWorkspaces.detail.columnSize\" | transloco }}</div>\n <div role=\"columnheader\">{{ \"documentWorkspaces.detail.columnDate\" | transloco }}</div>\n <div role=\"columnheader\"></div>\n </div>\n @for (document of filtered(); track document.id) {\n <document-row\n [document]=\"document\"\n [workspaceRole]=\"workspace.currentUserRole\"\n [query]=\"query()\"\n [active]=\"document.id === previewedDocumentId()\"\n [selecting]=\"selection.active()\"\n [selected]=\"selection.isSelected(document)\"\n [selectable]=\"selection.canSelect(document)\"\n (opened)=\"togglePreview(document)\"\n (deleteRequested)=\"confirmDeleteDocuments([document])\"\n (selectedChange)=\"selection.toggle(document, $event)\" />\n }\n </div>\n }\n\n @if (hasMore()) {\n <div class=\"flex flex-col items-center gap-2\">\n @if (loadMoreError(); as message) {\n <p role=\"alert\" class=\"text-(--font-error-base) text-sm\">{{ \"documentWorkspaces.detail.loadMoreError\" | transloco: { error: message } }}</p>\n }\n <button variant=\"secondary\" size=\"xs\" [disabled]=\"loadingMore()\" (click)=\"loadMore()\">\n {{ \"documentWorkspaces.detail.loadMore\" | transloco }}\n </button>\n </div>\n }\n } @else {\n <p class=\"text-(--font-primary-muted) py-8 text-center text-sm\" data-testid=\"not-found\">{{ \"documentWorkspaces.errors.notFound\" | transloco }}</p>\n }\n }\n }\n </div>\n </div>\n\n <!-- The list gives up part of its width to the preview, and takes it back when the preview closes. -->\n <div\n #handle=\"resizeHandle\"\n class=\"group/handle flex w-3 shrink-0 cursor-col-resize items-center justify-center max-md:hidden\"\n [class.hidden]=\"!previewed()\"\n [resizeHandle]=\"previewWidth()\"\n (resizeHandleChange)=\"userPreviewWidth.set($event)\"\n [resizeMin]=\"280\"\n [resizeMax]=\"maxPreviewWidth()\"\n [attr.aria-label]=\"'documentWorkspaces.preview.resize' | transloco\">\n <span class=\"h-6 w-0.5 rounded-full bg-(--stroke-primary-light) opacity-0 group-hover/handle:opacity-100 group-focus-visible/handle:opacity-100 group-data-resizing/handle:opacity-100\"></span>\n </div>\n <aside\n class=\"sticky top-3 my-3 mr-3 flex h-[calc(100dvh-1.5rem)] max-h-[calc(100%-1.5rem)] shrink-0 self-start overflow-hidden rounded-(--radius-xl) bg-(--bg-neutral-white) transition-[width] duration-[350ms] ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none data-open:border data-open:border-(--stroke-primary-lighter) data-open:shadow-[0_4px_6px_-2px_rgba(45,57,58,0.05),0_10px_15px_-3px_rgba(45,57,58,0.1)] max-md:data-open:fixed max-md:data-open:inset-0 max-md:data-open:z-50 max-md:data-open:m-0 max-md:data-open:w-full! max-md:data-open:max-h-none max-md:data-open:rounded-none\"\n [class.transition-none]=\"handle.resizing()\"\n [style.width.px]=\"previewed() ? previewWidth() : 0\"\n [attr.data-open]=\"previewed() ? '' : null\"\n [attr.aria-hidden]=\"previewed() ? null : 'true'\"\n data-testid=\"preview\">\n @if (previewed(); as previewed) {\n <!-- Its own width, so the content does not reflow while the card opens. -->\n <document-preview-panel\n class=\"shrink-0 max-md:w-full!\"\n [style.width.px]=\"previewWidth()\"\n [document]=\"previewed\"\n [queryName]=\"queryName()\"\n (closed)=\"previewedDocumentId.set(null)\"\n (askFollowUp)=\"askFollowUp.emit($event)\" />\n }\n </aside>\n ", isInline: true, dependencies: [{ kind: "component", type: ChevronDownIcon, selector: "chevron-down, ChevronDown, chevrondown, chevron-down-icon, ChevronDownIcon, chevrondownicon", inputs: ["class"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: DocumentCard, selector: "document-card", inputs: ["document", "workspaceRole", "query", "active", "selecting", "selected", "selectable"], outputs: ["opened", "deleteRequested", "selectedChange"] }, { kind: "component", type: DocumentPreviewPanel, selector: "document-preview-panel", inputs: ["document", "queryName"], outputs: ["closed", "askFollowUp"] }, { kind: "component", type: DocumentRow, selector: "document-row", inputs: ["document", "workspaceRole", "query", "active", "selecting", "selected", "selectable"], outputs: ["opened", "deleteRequested", "selectedChange"] }, { kind: "component", type: EmptyFolderIcon, selector: "empty-folder-icon" }, { kind: "component", type: GridIcon, selector: "grid-icon", inputs: ["class"] }, { kind: "component", type: ListIcon, selector: "list-icon, ListIcon", inputs: ["class"] }, { kind: "component", type: MagnifyingGlassIcon, selector: "magnifying-glass-icon, MagnifyingGlassIcon", inputs: ["class"] }, { kind: "component", type: NotFoundIcon, selector: "not-found-icon" }, { kind: "component", type: PenToSquareIcon, selector: "pen-to-square-icon, PenToSquareIcon", inputs: ["class"] }, { kind: "component", type: RefreshIcon, selector: "refresh-icon, RefreshIcon, refreshicon", inputs: ["class"] }, { kind: "directive", type: ResizeHandle, selector: "[resizeHandle]", inputs: ["resizeHandle", "resizeMin", "resizeMax", "resizeStep", "resizeEdge"], outputs: ["resizeHandleChange"], exportAs: ["resizeHandle"] }, { kind: "component", type: TagIcon, selector: "tag-icon, TagIcon", inputs: ["class"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip]", inputs: ["tooltip", "strategy", "tooltip-position", "tooltip-delay", "tooltip-duration", "tooltip-offset"] }, { kind: "component", type: TrashIcon, selector: "trash-icon, TrashIcon", inputs: ["class"] }, { kind: "component", type: UploadIcon, selector: "upload-icon, UploadIcon, uploadicon", inputs: ["class"] }, { kind: "component", type: UserRoundIcon, selector: "user-round, UserRound, userround, user-round-icon, UserRoundIcon, userroundicon", inputs: ["class"] }, { kind: "component", type: WorkspaceMessage, selector: "workspace-message", inputs: ["message"] }, { kind: "directive", type: WriterOnly, selector: "[writerOnly]", inputs: ["writerOnly"] }, { kind: "component", type: XMarkIcon, selector: "XMarkIcon, xmark-icon, x-mark-icon", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5850
6016
|
}
|
|
5851
6017
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: WorkspaceDetail, decorators: [{
|
|
5852
6018
|
type: Component,
|
|
@@ -5863,6 +6029,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
5863
6029
|
GridIcon,
|
|
5864
6030
|
ListIcon,
|
|
5865
6031
|
MagnifyingGlassIcon,
|
|
6032
|
+
NotFoundIcon,
|
|
5866
6033
|
PenToSquareIcon,
|
|
5867
6034
|
RefreshIcon,
|
|
5868
6035
|
ResizeHandle,
|
|
@@ -5886,7 +6053,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
5886
6053
|
[iconOnly]="true"
|
|
5887
6054
|
[attr.aria-label]="'documentWorkspaces.detail.back' | transloco"
|
|
5888
6055
|
[tooltip]="'documentWorkspaces.detail.back' | transloco"
|
|
5889
|
-
(click)="
|
|
6056
|
+
(click)="leave()">
|
|
5890
6057
|
<chevron-down-icon class="rotate-90" />
|
|
5891
6058
|
</button>
|
|
5892
6059
|
|
|
@@ -5894,34 +6061,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
5894
6061
|
<h1 class="min-w-28 flex-[1_1_16rem] text-2xl leading-[31px] font-bold break-words text-(--font-neutral-black)">{{ workspace.name }}</h1>
|
|
5895
6062
|
|
|
5896
6063
|
<!-- Beside the title while they fit next to its 16rem, on a line of their own under it once they do not. -->
|
|
5897
|
-
|
|
5898
|
-
<
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
<
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
<
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6064
|
+
@if (!deletedElsewhere()) {
|
|
6065
|
+
<div class="ml-auto flex shrink-0 items-center gap-1">
|
|
6066
|
+
<button size="sm"
|
|
6067
|
+
variant="ghost"
|
|
6068
|
+
class="gap-1.5 text-(--font-error-base) hover:bg-(--bg-error-lightest)"
|
|
6069
|
+
[writerOnly]="workspace.currentUserRole"
|
|
6070
|
+
(click)="confirmDeleteWorkspace(workspace)">
|
|
6071
|
+
<trash-icon />
|
|
6072
|
+
{{ "documentWorkspaces.common.delete" | transloco }}
|
|
6073
|
+
</button>
|
|
6074
|
+
<button size="sm" variant="ghost" class="gap-1.5" [writerOnly]="workspace.currentUserRole" (click)="share(workspace)">
|
|
6075
|
+
<user-round-icon />
|
|
6076
|
+
{{ "documentWorkspaces.share.title" | transloco }}
|
|
6077
|
+
</button>
|
|
6078
|
+
<button size="sm" variant="ghost" class="gap-1.5" [writerOnly]="workspace.currentUserRole" (click)="edit(workspace)">
|
|
6079
|
+
<pen-to-square-icon />
|
|
6080
|
+
{{ "documentWorkspaces.common.edit" | transloco }}
|
|
6081
|
+
</button>
|
|
6082
|
+
<button size="sm"
|
|
6083
|
+
variant="ghost"
|
|
6084
|
+
class="w-6"
|
|
6085
|
+
[iconOnly]="true"
|
|
6086
|
+
[disabled]="refreshing()"
|
|
6087
|
+
[attr.aria-label]="'documentWorkspaces.common.refresh' | transloco"
|
|
6088
|
+
[tooltip]="'documentWorkspaces.common.refresh' | transloco"
|
|
6089
|
+
(click)="refresh()">
|
|
6090
|
+
<refresh-icon />
|
|
6091
|
+
</button>
|
|
6092
|
+
</div>
|
|
6093
|
+
}
|
|
5925
6094
|
}
|
|
5926
6095
|
</div>
|
|
5927
6096
|
|
|
@@ -5936,6 +6105,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
5936
6105
|
</p>
|
|
5937
6106
|
}
|
|
5938
6107
|
|
|
6108
|
+
@if (!deletedElsewhere()) {
|
|
5939
6109
|
<div class="mb-3 flex flex-wrap items-center gap-2">
|
|
5940
6110
|
<div role="group" class="inline-flex gap-1 rounded-full bg-(--bg-primary-lighter) p-0.5">
|
|
5941
6111
|
<button
|
|
@@ -6006,6 +6176,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
6006
6176
|
{{ "documentWorkspaces.detail.fileCount" | transloco: { count: workspace.documentCount } }}
|
|
6007
6177
|
}
|
|
6008
6178
|
</p>
|
|
6179
|
+
}
|
|
6009
6180
|
}
|
|
6010
6181
|
|
|
6011
6182
|
@if (refreshError(); as message) {
|
|
@@ -6031,7 +6202,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
6031
6202
|
</div>
|
|
6032
6203
|
}
|
|
6033
6204
|
@default {
|
|
6034
|
-
@if (
|
|
6205
|
+
@if (deletedElsewhere()) {
|
|
6206
|
+
<div class="flex flex-col items-center gap-1 py-8 text-center" data-testid="not-found">
|
|
6207
|
+
<not-found-icon />
|
|
6208
|
+
<p class="max-w-70 text-sm text-(--font-primary-base)">{{ "documentWorkspaces.errors.notFound" | transloco }}</p>
|
|
6209
|
+
<button variant="secondary" size="sm" class="mt-2" (click)="leave()">{{ "documentWorkspaces.detail.back" | transloco }}</button>
|
|
6210
|
+
</div>
|
|
6211
|
+
} @else if (workspace(); as workspace) {
|
|
6035
6212
|
@if (!documents().length) {
|
|
6036
6213
|
<div class="flex flex-col items-center gap-1 py-8 text-center" data-testid="empty">
|
|
6037
6214
|
<empty-folder-icon />
|