@api-client/ui 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +8 -0
- package/demo/elements/index.html +3 -0
- package/demo/elements/store/file-picker.html +15 -0
- package/demo/elements/store/file-picker.ts +134 -0
- package/demo/elements/store/index.html +19 -0
- package/demo/store/StorePlugin.js +1 -0
- package/dist/bindings/base/StoreBindings.d.ts +5 -0
- package/dist/bindings/base/StoreBindings.d.ts.map +1 -1
- package/dist/bindings/base/StoreBindings.js +15 -1
- package/dist/bindings/base/StoreBindings.js.map +1 -1
- package/dist/define/store/file-picker.d.ts +9 -0
- package/dist/define/store/file-picker.d.ts.map +1 -0
- package/dist/define/store/file-picker.js +10 -0
- package/dist/define/store/file-picker.js.map +1 -0
- package/dist/define/{files → store}/share-file.d.ts +1 -1
- package/dist/define/store/share-file.d.ts.map +1 -0
- package/dist/define/{files → store}/share-file.js +2 -2
- package/dist/define/store/share-file.js.map +1 -0
- package/dist/elements/store/FilePicker.element.d.ts +87 -0
- package/dist/elements/store/FilePicker.element.d.ts.map +1 -0
- package/dist/elements/store/FilePicker.element.js +263 -0
- package/dist/elements/store/FilePicker.element.js.map +1 -0
- package/dist/elements/store/FilePicker.styles.d.ts +3 -0
- package/dist/elements/store/FilePicker.styles.d.ts.map +1 -0
- package/dist/elements/store/FilePicker.styles.js +72 -0
- package/dist/elements/store/FilePicker.styles.js.map +1 -0
- package/dist/elements/store/FilesLib.d.ts +10 -0
- package/dist/elements/store/FilesLib.d.ts.map +1 -0
- package/dist/elements/store/FilesLib.js +38 -0
- package/dist/elements/store/FilesLib.js.map +1 -0
- package/dist/elements/{files/ShareFile.d.ts → store/ShareFile.element.d.ts} +1 -1
- package/dist/elements/store/ShareFile.element.d.ts.map +1 -0
- package/dist/elements/{files/ShareFile.js → store/ShareFile.element.js} +1 -1
- package/dist/elements/store/ShareFile.element.js.map +1 -0
- package/dist/elements/store/ShareFile.styles.d.ts.map +1 -0
- package/dist/elements/{files → store}/ShareFile.styles.js.map +1 -1
- package/dist/events/EventTypes.d.ts +1 -0
- package/dist/events/EventTypes.d.ts.map +1 -1
- package/dist/events/EventTypes.js +1 -0
- package/dist/events/EventTypes.js.map +1 -1
- package/dist/events/Events.d.ts +1 -0
- package/dist/events/Events.d.ts.map +1 -1
- package/dist/events/StoreEvents.d.ts +8 -1
- package/dist/events/StoreEvents.d.ts.map +1 -1
- package/dist/events/StoreEvents.js +19 -0
- package/dist/events/StoreEvents.js.map +1 -1
- package/dist/pages/ApplicationScreen.d.ts +1 -1
- package/dist/pages/ApplicationScreen.d.ts.map +1 -1
- package/dist/pages/ApplicationScreen.js +4 -2
- package/dist/pages/ApplicationScreen.js.map +1 -1
- package/dist/pages/api-client/ApiClient.screen.d.ts +0 -6
- package/dist/pages/api-client/ApiClient.screen.d.ts.map +1 -1
- package/dist/pages/api-client/ApiClient.screen.js +16 -29
- package/dist/pages/api-client/ApiClient.screen.js.map +1 -1
- package/dist/pages/api-client/Authenticate.screen.d.ts +1 -1
- package/dist/pages/api-client/Authenticate.screen.d.ts.map +1 -1
- package/dist/pages/api-client/Authenticate.screen.js +2 -2
- package/dist/pages/api-client/Authenticate.screen.js.map +1 -1
- package/dist/pages/api-client/pages/Files.page.d.ts +6 -35
- package/dist/pages/api-client/pages/Files.page.d.ts.map +1 -1
- package/dist/pages/api-client/pages/Files.page.js +32 -141
- package/dist/pages/api-client/pages/Files.page.js.map +1 -1
- package/dist/pages/api-client/pages/Shared.page.d.ts +1 -5
- package/dist/pages/api-client/pages/Shared.page.d.ts.map +1 -1
- package/dist/pages/api-client/pages/Shared.page.js +1 -40
- package/dist/pages/api-client/pages/Shared.page.js.map +1 -1
- package/dist/pages/demo/DemoPage.d.ts +7 -0
- package/dist/pages/demo/DemoPage.d.ts.map +1 -1
- package/dist/pages/demo/DemoPage.js +14 -0
- package/dist/pages/demo/DemoPage.js.map +1 -1
- package/dist/store/FileSystem.d.ts +90 -0
- package/dist/store/FileSystem.d.ts.map +1 -0
- package/dist/store/FileSystem.js +260 -0
- package/dist/store/FileSystem.js.map +1 -0
- package/dist/styles/global-styles.d.ts.map +1 -1
- package/dist/styles/global-styles.js +7 -0
- package/dist/styles/global-styles.js.map +1 -1
- package/dist/ui/icons/Icons.d.ts +2 -1
- package/dist/ui/icons/Icons.d.ts.map +1 -1
- package/dist/ui/icons/Icons.js +1 -0
- package/dist/ui/icons/Icons.js.map +1 -1
- package/dist/ui/list/UiDropdownList.d.ts +9 -1
- package/dist/ui/list/UiDropdownList.d.ts.map +1 -1
- package/dist/ui/list/UiDropdownList.js +39 -17
- package/dist/ui/list/UiDropdownList.js.map +1 -1
- package/dist/ui/list/UiList.d.ts +6 -1
- package/dist/ui/list/UiList.d.ts.map +1 -1
- package/dist/ui/list/UiList.js +24 -9
- package/dist/ui/list/UiList.js.map +1 -1
- package/dist/ui/table/DataTable.d.ts +4 -0
- package/dist/ui/table/DataTable.d.ts.map +1 -1
- package/dist/ui/table/DataTable.js +23 -1
- package/dist/ui/table/DataTable.js.map +1 -1
- package/package.json +1 -1
- package/src/bindings/base/StoreBindings.ts +16 -1
- package/src/define/store/file-picker.ts +12 -0
- package/src/define/{files → store}/share-file.ts +2 -2
- package/src/elements/store/FilePicker.element.ts +297 -0
- package/src/elements/store/FilePicker.styles.ts +72 -0
- package/src/elements/store/FilesLib.ts +32 -0
- package/src/events/EventTypes.ts +1 -0
- package/src/events/StoreEvents.ts +21 -1
- package/src/pages/ApplicationScreen.ts +5 -3
- package/src/pages/api-client/ApiClient.screen.ts +16 -31
- package/src/pages/api-client/Authenticate.screen.ts +2 -2
- package/src/pages/api-client/pages/Files.page.ts +37 -164
- package/src/pages/api-client/pages/Shared.page.ts +2 -40
- package/src/pages/demo/DemoPage.ts +17 -0
- package/src/store/FileSystem.ts +325 -0
- package/src/styles/global-styles.ts +7 -0
- package/src/ui/icons/Icons.ts +2 -1
- package/src/ui/list/UiDropdownList.ts +44 -17
- package/src/ui/list/UiList.ts +26 -10
- package/src/ui/table/DataTable.ts +29 -3
- package/test/elements/store/FilePicker.test.ts +241 -0
- package/test/env.js +3 -0
- package/test/helpers/StoreHelper.ts +390 -0
- package/tsconfig.eslint.json +3 -1
- package/web-test-runner.config.mjs +49 -3
- package/dist/define/files/share-file.d.ts.map +0 -1
- package/dist/define/files/share-file.js.map +0 -1
- package/dist/elements/files/ShareFile.d.ts.map +0 -1
- package/dist/elements/files/ShareFile.js.map +0 -1
- package/dist/elements/files/ShareFile.styles.d.ts.map +0 -1
- /package/dist/elements/{files → store}/ShareFile.styles.d.ts +0 -0
- /package/dist/elements/{files → store}/ShareFile.styles.js +0 -0
- /package/src/elements/{files/ShareFile.ts → store/ShareFile.element.ts} +0 -0
- /package/src/elements/{files → store}/ShareFile.styles.ts +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
import typography from '../../styles/m3/typography.module.js';
|
|
3
|
+
|
|
4
|
+
export default [typography, css`
|
|
5
|
+
:host {
|
|
6
|
+
display: block;
|
|
7
|
+
|
|
8
|
+
width: 420px;
|
|
9
|
+
height: 400px;
|
|
10
|
+
|
|
11
|
+
background-color: var(--md-sys-color-background);
|
|
12
|
+
color: var(--md-sys-color-on-background);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.content {
|
|
16
|
+
height: 100%;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.header {
|
|
22
|
+
height: 56px;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
position: relative;
|
|
26
|
+
margin: 0 16px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.header.withIcon {
|
|
30
|
+
margin: 0 16px 0 4px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.header .label {
|
|
34
|
+
display: block;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.progress {
|
|
38
|
+
position: absolute;
|
|
39
|
+
bottom: -10px; /* aligns with the divider */
|
|
40
|
+
width: 100%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.list {
|
|
44
|
+
overflow-y: auto;
|
|
45
|
+
flex: 1;
|
|
46
|
+
margin: 0;
|
|
47
|
+
padding: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.actions {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: flex-end;
|
|
54
|
+
margin: 0 16px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.file-icon {
|
|
58
|
+
margin-right: 12px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.error {
|
|
62
|
+
background-color: var(--md-sys-color-error-container);
|
|
63
|
+
color: var(--md-sys-color-on-error-container);
|
|
64
|
+
display: block;
|
|
65
|
+
padding: 8px 12px;
|
|
66
|
+
border-radius: var(--md-sys-shape-corner-small);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.no-files {
|
|
70
|
+
flex: 1;
|
|
71
|
+
}
|
|
72
|
+
`];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CertificateFileKind, DataFileKind, FolderKind, IFile, ProjectKind } from "@api-client/core/build/browser.js";
|
|
2
|
+
import { IconType } from "../../ui/icons/Icons.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sorts files on the list.
|
|
6
|
+
*
|
|
7
|
+
* Folders are always on top.
|
|
8
|
+
*/
|
|
9
|
+
export function filesSortFunction(a: IFile, b: IFile): number {
|
|
10
|
+
if (a.kind !== b.kind) {
|
|
11
|
+
if (a.kind === FolderKind) {
|
|
12
|
+
return -1;
|
|
13
|
+
}
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
if (a.kind === b.kind && a.kind === FolderKind) {
|
|
17
|
+
return (a.info.name || '').localeCompare(b.info.name || '');
|
|
18
|
+
}
|
|
19
|
+
return b.lastModified.time - a.lastModified.time;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function fileIcon(item: IFile): IconType | undefined {
|
|
23
|
+
let icon: IconType | undefined;
|
|
24
|
+
switch (item.kind) {
|
|
25
|
+
case DataFileKind: icon = 'schema'; break;
|
|
26
|
+
case FolderKind: icon = 'folder'; break;
|
|
27
|
+
case ProjectKind: icon = 'collectionsBookmark'; break;
|
|
28
|
+
case CertificateFileKind: icon = 'certificate'; break;
|
|
29
|
+
default:
|
|
30
|
+
}
|
|
31
|
+
return icon;
|
|
32
|
+
}
|
package/src/events/EventTypes.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
IBackendInfo, IFile, IFileCreateOptions,
|
|
4
4
|
ListFileKind, AccessOperation, IUser, IPatchRevision, IApplication, IHttpHistory, IHttpHistoryBulkAdd, HistoryListOptions,
|
|
5
|
-
ContextChangeRecord, ContextDeleteEvent, ContextDeleteBulkEvent, ContextListOptions, ContextListResult, IProjectExecution, IProjectExecutionListOptions, IBulkOperationResult,
|
|
5
|
+
ContextChangeRecord, ContextDeleteEvent, ContextDeleteBulkEvent, ContextListOptions, ContextListResult, IProjectExecution, IProjectExecutionListOptions, IBulkOperationResult, FileBreadcrumb,
|
|
6
6
|
} from '@api-client/core/build/browser.js';
|
|
7
7
|
import { Patch } from '@api-client/json';
|
|
8
8
|
import { IConfigEnvironment, IConfigInit } from '../lib/config/Config.js';
|
|
@@ -371,6 +371,26 @@ export const StoreEvents = Object.freeze({
|
|
|
371
371
|
target.dispatchEvent(e);
|
|
372
372
|
return ((e.detail.result as unknown) as Promise<ContextListResult<IUser>>);
|
|
373
373
|
},
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Lists breadcrumbs to the file.
|
|
377
|
+
*
|
|
378
|
+
* @param fileOrFolder The if of the file or folder to query for.
|
|
379
|
+
* @param options Optional query options.
|
|
380
|
+
*/
|
|
381
|
+
breadcrumbs: async (fileOrFolder: string, target: EventTarget = document.body): Promise<ContextListResult<FileBreadcrumb>> => {
|
|
382
|
+
const e = new CustomEvent(EventTypes.Store.File.breadcrumbs, {
|
|
383
|
+
bubbles: true,
|
|
384
|
+
cancelable: true,
|
|
385
|
+
composed: true,
|
|
386
|
+
detail: {
|
|
387
|
+
key: fileOrFolder,
|
|
388
|
+
result: undefined,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
target.dispatchEvent(e);
|
|
392
|
+
return ((e.detail.result as unknown) as Promise<ContextListResult<FileBreadcrumb>>);
|
|
393
|
+
},
|
|
374
394
|
}),
|
|
375
395
|
/**
|
|
376
396
|
* Reads the current user.
|
|
@@ -144,7 +144,9 @@ export abstract class ApplicationScreen extends RouteMixin(RenderableMixin(Event
|
|
|
144
144
|
window.onunhandledrejection = this.unhandledRejectionHandler.bind(this);
|
|
145
145
|
this.initMediaQueries();
|
|
146
146
|
this.initStoreChange();
|
|
147
|
-
this.configBroadcast.addEventListener('message', this.handleConfigBroadcast.bind(this))
|
|
147
|
+
this.configBroadcast.addEventListener('message', this.handleConfigBroadcast.bind(this));
|
|
148
|
+
// this will use the system-default theme until config is read.
|
|
149
|
+
this.themeActiveCallback();
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
/**
|
|
@@ -325,7 +327,7 @@ export abstract class ApplicationScreen extends RouteMixin(RenderableMixin(Event
|
|
|
325
327
|
* Called from the initialize store init flow when the user is not authenticated
|
|
326
328
|
* and the auth screen is rendered.
|
|
327
329
|
*/
|
|
328
|
-
protected async
|
|
330
|
+
protected async handleAuthenticate(): Promise<void> {
|
|
329
331
|
const { store } = this.bindings;
|
|
330
332
|
await store.auth.authenticate({ updateEnvironment: true });
|
|
331
333
|
this.authenticated = true;
|
|
@@ -509,7 +511,7 @@ export abstract class ApplicationScreen extends RouteMixin(RenderableMixin(Event
|
|
|
509
511
|
<div class="auth-required-screen">
|
|
510
512
|
<h1 class="display-large">Authentication required</h1>
|
|
511
513
|
<p class="body-large">You are not authenticated. To continue, please, authenticate your account.</p>
|
|
512
|
-
<ui-button type="tonal" @click="${this.
|
|
514
|
+
<ui-button type="tonal" @click="${this.handleAuthenticate}">Authenticate</ui-button>
|
|
513
515
|
</div>
|
|
514
516
|
`;
|
|
515
517
|
}
|
|
@@ -99,8 +99,6 @@ export default class ApiClientScreen extends ApplicationScreen {
|
|
|
99
99
|
|
|
100
100
|
protected settings = new SettingsPage(this);
|
|
101
101
|
|
|
102
|
-
filesChannel = new BroadcastChannel(StoreBroadcast.files);
|
|
103
|
-
|
|
104
102
|
spacesChannel = new BroadcastChannel(StoreBroadcast.spaces);
|
|
105
103
|
|
|
106
104
|
trashChannel = new BroadcastChannel(StoreBroadcast.trash);
|
|
@@ -136,7 +134,6 @@ export default class ApiClientScreen extends ApplicationScreen {
|
|
|
136
134
|
await this.bindings.store.observeTrash();
|
|
137
135
|
}
|
|
138
136
|
await this.loadUser();
|
|
139
|
-
this.filesChannel.addEventListener('message', this.fileMetaMessageHandler.bind(this));
|
|
140
137
|
this.spacesChannel.addEventListener('message', this.spacesMessageHandler.bind(this));
|
|
141
138
|
this.trashChannel.addEventListener('message', this.trashMessageHandler.bind(this));
|
|
142
139
|
this.initializeRouting();
|
|
@@ -146,8 +143,8 @@ export default class ApiClientScreen extends ApplicationScreen {
|
|
|
146
143
|
reset(): void {
|
|
147
144
|
this.spaces = [];
|
|
148
145
|
this.spacesCursor = undefined;
|
|
149
|
-
this.files.resetList();
|
|
150
|
-
this.shared.resetList();
|
|
146
|
+
this.files.fs.resetList();
|
|
147
|
+
this.shared.fs.resetList();
|
|
151
148
|
this.trash.resetList();
|
|
152
149
|
}
|
|
153
150
|
|
|
@@ -249,16 +246,16 @@ export default class ApiClientScreen extends ApplicationScreen {
|
|
|
249
246
|
this.bindings.store.space = this.selectedSpace;
|
|
250
247
|
|
|
251
248
|
this.render();
|
|
252
|
-
this.files.resetList();
|
|
253
|
-
this.shared.resetList();
|
|
249
|
+
this.files.fs.resetList();
|
|
250
|
+
this.shared.fs.resetList();
|
|
254
251
|
this.trash.resetList();
|
|
255
252
|
if (this.page === 'files') {
|
|
256
|
-
this.files.
|
|
253
|
+
this.files.fs.debounceQuery();
|
|
257
254
|
navigate('files'); // remove any folder info
|
|
258
255
|
} else if (this.page === 'trash') {
|
|
259
256
|
this.trash.readFiles();
|
|
260
257
|
} else if (this.page === 'shared') {
|
|
261
|
-
this.shared.
|
|
258
|
+
this.shared.fs.debounceQuery();
|
|
262
259
|
}
|
|
263
260
|
this.storeLastSpaceKey(this.selectedSpace);
|
|
264
261
|
}
|
|
@@ -279,18 +276,6 @@ export default class ApiClientScreen extends ApplicationScreen {
|
|
|
279
276
|
this.spacesCursor = cursor;
|
|
280
277
|
}
|
|
281
278
|
|
|
282
|
-
/**
|
|
283
|
-
* A handler for the broadcast channel message which informs about changes to files meta.
|
|
284
|
-
* The function distributes changes between pages
|
|
285
|
-
*/
|
|
286
|
-
protected fileMetaMessageHandler(e: MessageEvent): void {
|
|
287
|
-
const event = e.data as BroadcastEvent;
|
|
288
|
-
if (event.space === this.selectedSpace) {
|
|
289
|
-
this.files.handleFileEvent(event);
|
|
290
|
-
}
|
|
291
|
-
this.shared.handleFileEvent(event);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
279
|
protected trashMessageHandler(e: MessageEvent): void {
|
|
295
280
|
const event = e.data as BroadcastEvent;
|
|
296
281
|
if (event.space !== this.selectedSpace) {
|
|
@@ -343,13 +328,13 @@ export default class ApiClientScreen extends ApplicationScreen {
|
|
|
343
328
|
this.resetRoute();
|
|
344
329
|
this.page = 'files';
|
|
345
330
|
const folder = info.params && info.params.key as string | undefined;
|
|
346
|
-
const folderChanged = this.files.
|
|
331
|
+
const folderChanged = this.files.fs.parent !== folder;
|
|
347
332
|
if (folderChanged) {
|
|
348
|
-
this.files.
|
|
349
|
-
this.files.resetList();
|
|
333
|
+
this.files.fs.parent = folder;
|
|
334
|
+
this.files.fs.resetList();
|
|
350
335
|
}
|
|
351
|
-
if (!this.files.files.length || folderChanged) {
|
|
352
|
-
this.files.
|
|
336
|
+
if (!this.files.fs.files.length || folderChanged) {
|
|
337
|
+
this.files.fs.debounceQuery();
|
|
353
338
|
}
|
|
354
339
|
}
|
|
355
340
|
|
|
@@ -357,13 +342,13 @@ export default class ApiClientScreen extends ApplicationScreen {
|
|
|
357
342
|
this.resetRoute();
|
|
358
343
|
this.page = 'shared';
|
|
359
344
|
const folder = info.params && info.params.key as string | undefined;
|
|
360
|
-
const folderChanged = this.shared.
|
|
345
|
+
const folderChanged = this.shared.fs.parent !== folder;
|
|
361
346
|
if (folderChanged) {
|
|
362
|
-
this.shared.
|
|
363
|
-
this.shared.resetList();
|
|
347
|
+
this.shared.fs.parent = folder;
|
|
348
|
+
this.shared.fs.resetList();
|
|
364
349
|
}
|
|
365
|
-
if (!this.shared.files.length || folderChanged) {
|
|
366
|
-
this.shared.
|
|
350
|
+
if (!this.shared.fs.files.length || folderChanged) {
|
|
351
|
+
this.shared.fs.debounceQuery();
|
|
367
352
|
}
|
|
368
353
|
}
|
|
369
354
|
|
|
@@ -66,7 +66,7 @@ export default class AuthenticateScreen extends ApplicationScreen {
|
|
|
66
66
|
return env;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
protected
|
|
69
|
+
protected async handleAuth(): Promise<void> {
|
|
70
70
|
const { environment } = this;
|
|
71
71
|
if (!environment) {
|
|
72
72
|
this.environmentError = 'Environment information not found. Go back to the previous page.';
|
|
@@ -146,7 +146,7 @@ export default class AuthenticateScreen extends ApplicationScreen {
|
|
|
146
146
|
<div class="action">
|
|
147
147
|
<ui-button
|
|
148
148
|
type="filled"
|
|
149
|
-
@click="${this.
|
|
149
|
+
@click="${this.handleAuth}"
|
|
150
150
|
>Authenticate</ui-button>
|
|
151
151
|
</div>
|
|
152
152
|
`;
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IFile, HttpProject, Project, Folder, ProjectKind, FolderKind,
|
|
3
3
|
EventUtils, DataNamespace, DataFile, DataFileKind,
|
|
4
|
-
FileAddOptions, FileBreadcrumb,
|
|
5
|
-
BroadcastEvent, BroadcastFileData,
|
|
6
|
-
FileMetaCreatedBroadcastEvent,
|
|
7
|
-
DeletedBroadcastEvent,
|
|
8
|
-
FilePatchBroadcastEvent,
|
|
9
|
-
ContextListResult,
|
|
4
|
+
FileAddOptions, FileBreadcrumb,
|
|
10
5
|
ICapabilities,
|
|
11
6
|
} from "@api-client/core/build/browser.js";
|
|
12
7
|
import { html, nothing, TemplateResult } from "lit";
|
|
13
8
|
import { join } from "lit/directives/join.js";
|
|
14
9
|
import { map } from "lit/directives/map.js";
|
|
15
10
|
import { until } from 'lit/directives/until.js';
|
|
16
|
-
import { Patch } from "@api-client/json";
|
|
17
11
|
import type ApiClientScreen from "../ApiClient.screen.js";
|
|
18
12
|
import { DataTable } from "../../../ui/table/DataTable.js";
|
|
19
13
|
import { NavigationEvents } from "../../../events/NavigationEvents.js";
|
|
@@ -22,9 +16,10 @@ import { DataImportProcessor } from "../../../http-client/store/DataImportProces
|
|
|
22
16
|
import { SnackNotifications } from "../../../ui/notification/SnackNotifications.js";
|
|
23
17
|
import { UiDialogClosingReason } from "../../../ui/dialog/UiDialog.js";
|
|
24
18
|
import { FileBindings } from "../../../bindings/base/FileBindings.js";
|
|
25
|
-
import { IconType } from "../../../ui/icons/Icons.js";
|
|
26
19
|
import UserCache from '../../../store/UserCache.js';
|
|
27
20
|
import AppInfo from '../AppInfo.js';
|
|
21
|
+
import { fileIcon } from "../../../elements/store/FilesLib.js";
|
|
22
|
+
import { FileSystem } from "../../../store/FileSystem.js";
|
|
28
23
|
import '@github/relative-time-element';
|
|
29
24
|
import '../../../define/ui/ui-list.js';
|
|
30
25
|
import '../../../define/ui/ui-list-item.js';
|
|
@@ -35,7 +30,7 @@ import '../../../define/ui/ui-button.js';
|
|
|
35
30
|
import '../../../define/ui/ui-divider.js';
|
|
36
31
|
import '../../../define/ui/ui-text-field.js';
|
|
37
32
|
import '../../../define/dialog/rename-dialog.js';
|
|
38
|
-
import '../../../define/
|
|
33
|
+
import '../../../define/store/share-file.js';
|
|
39
34
|
|
|
40
35
|
enum ImportState {
|
|
41
36
|
Analyzing,
|
|
@@ -49,41 +44,20 @@ enum ImportState {
|
|
|
49
44
|
* A class that specializes in rendering the list of files in HTTP Client.
|
|
50
45
|
*/
|
|
51
46
|
export class FilesPage {
|
|
47
|
+
fs = new FileSystem();
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
* The list of files to render.
|
|
55
|
-
* Do not overwrite this property or when you must then update the DataTable's `items` as well.
|
|
56
|
-
*/
|
|
57
|
-
files: IFile[] = [];
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* The pagination cursor for files.
|
|
61
|
-
* As long there's a cursor, there are more files to request.
|
|
62
|
-
*/
|
|
63
|
-
cursor?: string;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The key of the currently rendered folder.
|
|
67
|
-
*/
|
|
68
|
-
folder?: string;
|
|
69
|
-
|
|
70
|
-
breadcrumbs?: FileBreadcrumb[];
|
|
71
|
-
|
|
72
|
-
dt = new DataTable(() => this.files, { ariaLabel: 'Your files', active: true, selection: 'single' });
|
|
49
|
+
dt = new DataTable(() => this.fs.files, { ariaLabel: 'Your files', active: true, selection: 'single' });
|
|
73
50
|
|
|
74
51
|
importProcessor?: DataImportProcessor;
|
|
75
52
|
|
|
76
53
|
importState?: ImportState;
|
|
77
54
|
|
|
78
|
-
/**
|
|
79
|
-
* Whether the UI is reading files
|
|
80
|
-
*/
|
|
81
|
-
readingFiles?: boolean;
|
|
82
|
-
|
|
83
55
|
/**
|
|
84
56
|
* A selected in the table item.
|
|
85
57
|
*/
|
|
86
58
|
selectedItem?: string;
|
|
59
|
+
|
|
60
|
+
errorMessage?: string;
|
|
87
61
|
|
|
88
62
|
constructor(protected screen: ApiClientScreen) {
|
|
89
63
|
this.createProjectHandler = this.createProjectHandler.bind(this);
|
|
@@ -105,122 +79,24 @@ export class FilesPage {
|
|
|
105
79
|
this.dt.addEventListener('activate', this.itemActivateHandler.bind(this));
|
|
106
80
|
this.dt.addEventListener('render', this.handleTableRender.bind(this));
|
|
107
81
|
this.dt.addEventListener('select', this.handleTableSelect.bind(this));
|
|
108
|
-
|
|
82
|
+
this.dt.addEventListener('scrollend', this.handleTableScrollEnd.bind(this));
|
|
109
83
|
|
|
110
|
-
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
this.breadcrumbs = undefined;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async readFiles(): Promise<void> {
|
|
123
|
-
const { selectedSpace } = this.screen;
|
|
124
|
-
if (!selectedSpace) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (this.readingFiles) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
this.readingFiles = true;
|
|
131
|
-
const { cursor: filesCursor, folder } = this;
|
|
132
|
-
const opts: ContextSpaceListOptions = {
|
|
133
|
-
space: selectedSpace,
|
|
134
|
-
descending: true,
|
|
135
|
-
};
|
|
136
|
-
if (filesCursor) {
|
|
137
|
-
opts.cursor = filesCursor;
|
|
138
|
-
}
|
|
139
|
-
if (folder) {
|
|
140
|
-
opts.parent = folder;
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
const result = await this.listFiles(opts);
|
|
144
|
-
const { items, cursor } = result;
|
|
145
|
-
this.files = this.files.concat(items).sort(this.filesSortFunction.bind(this));
|
|
146
|
-
this.cursor = cursor;
|
|
147
|
-
if (folder) {
|
|
148
|
-
const br = await this.screen.bindings.store.files.breadcrumbs(folder);
|
|
149
|
-
this.breadcrumbs = br.items.reverse();
|
|
150
|
-
}
|
|
151
|
-
} finally {
|
|
152
|
-
this.readingFiles = false;
|
|
153
|
-
}
|
|
154
|
-
this.screen.render();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
protected async listFiles(opts: ContextSpaceListOptions): Promise<ContextListResult<IFile>> {
|
|
158
|
-
return this.screen.bindings.store.files.list(opts);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Sorts files on the list.
|
|
163
|
-
*
|
|
164
|
-
* Folders are always on top.
|
|
165
|
-
*/
|
|
166
|
-
filesSortFunction(a: IFile, b:IFile): number {
|
|
167
|
-
if (a.kind !== b.kind) {
|
|
168
|
-
if (a.kind === FolderKind) {
|
|
169
|
-
return -1;
|
|
170
|
-
}
|
|
171
|
-
return 1;
|
|
172
|
-
}
|
|
173
|
-
if (a.kind === b.kind && a.kind === FolderKind) {
|
|
174
|
-
return (a.info.name || '').localeCompare(b.info.name || '');
|
|
175
|
-
}
|
|
176
|
-
return b.lastModified.time - a.lastModified.time;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
handleFileEvent(event: BroadcastEvent): void {
|
|
180
|
-
const typed = event as BroadcastEvent & BroadcastFileData;
|
|
181
|
-
if (typed.parent && typed.parent !== this.folder) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (typed.alt !== 'meta') {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
switch (event.operation) {
|
|
188
|
-
case 'created': this.handleFileCreated((typed as FileMetaCreatedBroadcastEvent).data); break;
|
|
189
|
-
case 'deleted': this.handleFileDeleted(typed as DeletedBroadcastEvent); break;
|
|
190
|
-
case 'patch': this.handleFilePatch(typed as FilePatchBroadcastEvent); break;
|
|
191
|
-
default:
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
handleFileCreated(file: IFile): void {
|
|
196
|
-
this.files.push(file);
|
|
197
|
-
this.files.sort(this.filesSortFunction.bind(this));
|
|
198
|
-
this.screen.render();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
handleFileDeleted(event: DeletedBroadcastEvent): void {
|
|
202
|
-
this.removeFileFromList(event.key);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
removeFileFromList(key: string): void {
|
|
206
|
-
const index = this.files.findIndex(i => i.key === key);
|
|
207
|
-
if (index === -1) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
this.files.splice(index, 1);
|
|
211
|
-
this.screen.render();
|
|
84
|
+
this.fs.observe();
|
|
85
|
+
this.fs.addEventListener('change', () => this.screen.render());
|
|
86
|
+
this.fs.addEventListener('error', (e: Event) => {
|
|
87
|
+
const event = e as CustomEvent<string>;
|
|
88
|
+
this.errorMessage = event.detail;
|
|
89
|
+
});
|
|
90
|
+
this.fs.addEventListener('querycomplete', () => {
|
|
91
|
+
document.body.dispatchEvent(new Event('querycomplete', { bubbles: true }));
|
|
92
|
+
});
|
|
212
93
|
}
|
|
213
94
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const index = this.files.findIndex(i => i.key === key);
|
|
217
|
-
if (index === -1) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const file = this.files[index];
|
|
221
|
-
const result = Patch.apply(file, data.patch);
|
|
222
|
-
this.files[index] = result.doc as IFile;
|
|
95
|
+
async refresh(): Promise<void> {
|
|
96
|
+
this.fs.resetList();
|
|
223
97
|
this.screen.render();
|
|
98
|
+
await this.fs.queryPage();
|
|
99
|
+
await this.fs.queryBreadcrumbs();
|
|
224
100
|
}
|
|
225
101
|
|
|
226
102
|
protected cancelCreateProjectDialog(): void {
|
|
@@ -331,8 +207,8 @@ export class FilesPage {
|
|
|
331
207
|
throw new Error('You need to select a space first');
|
|
332
208
|
}
|
|
333
209
|
const opts: FileAddOptions = {};
|
|
334
|
-
if (this.
|
|
335
|
-
opts.parent = this.
|
|
210
|
+
if (this.fs.parent) {
|
|
211
|
+
opts.parent = this.fs.parent;
|
|
336
212
|
}
|
|
337
213
|
const result = await this.screen.bindings.store.files.add(file, opts);
|
|
338
214
|
if (media) {
|
|
@@ -422,7 +298,7 @@ export class FilesPage {
|
|
|
422
298
|
this.importState = ImportState.Importing;
|
|
423
299
|
this.screen.render();
|
|
424
300
|
try {
|
|
425
|
-
await importProcessor.importProject(this.screen.bindings.store, selectedSpace, this.
|
|
301
|
+
await importProcessor.importProject(this.screen.bindings.store, selectedSpace, this.fs.parent);
|
|
426
302
|
this.importState = ImportState.Done;
|
|
427
303
|
this.screen.render();
|
|
428
304
|
} catch (e) {
|
|
@@ -465,8 +341,12 @@ export class FilesPage {
|
|
|
465
341
|
this.screen.render();
|
|
466
342
|
}
|
|
467
343
|
|
|
344
|
+
protected handleTableScrollEnd(): void {
|
|
345
|
+
this.fs.debounceQuery();
|
|
346
|
+
}
|
|
347
|
+
|
|
468
348
|
openProject(key: string): void {
|
|
469
|
-
const file = this.files.find(i => i.key === key);
|
|
349
|
+
const file = this.fs.files.find(i => i.key === key);
|
|
470
350
|
if (!file) {
|
|
471
351
|
throw new Error(`Invalid state: file not found`);
|
|
472
352
|
}
|
|
@@ -550,7 +430,7 @@ export class FilesPage {
|
|
|
550
430
|
}
|
|
551
431
|
|
|
552
432
|
protected renameFile(key: string): void {
|
|
553
|
-
const file = this.files.find(i => i.key === key);
|
|
433
|
+
const file = this.fs.files.find(i => i.key === key);
|
|
554
434
|
if (!file) {
|
|
555
435
|
return;
|
|
556
436
|
}
|
|
@@ -571,7 +451,7 @@ export class FilesPage {
|
|
|
571
451
|
}
|
|
572
452
|
|
|
573
453
|
protected shareFile(key: string): void {
|
|
574
|
-
const file = this.files.find(i => i.key === key);
|
|
454
|
+
const file = this.fs.files.find(i => i.key === key);
|
|
575
455
|
if (!file) {
|
|
576
456
|
return;
|
|
577
457
|
}
|
|
@@ -582,7 +462,7 @@ export class FilesPage {
|
|
|
582
462
|
dialog.modal = true;
|
|
583
463
|
document.body.appendChild(dialog);
|
|
584
464
|
dialog.open = true;
|
|
585
|
-
dialog.addEventListener('
|
|
465
|
+
dialog.addEventListener('close', () => {
|
|
586
466
|
document.body.removeChild(dialog);
|
|
587
467
|
});
|
|
588
468
|
}
|
|
@@ -626,7 +506,7 @@ export class FilesPage {
|
|
|
626
506
|
if (!selectedItem) {
|
|
627
507
|
return nothing
|
|
628
508
|
}
|
|
629
|
-
const file = this.files.find(i => i.key === selectedItem);
|
|
509
|
+
const file = this.fs.files.find(i => i.key === selectedItem);
|
|
630
510
|
if (!file) {
|
|
631
511
|
return nothing;
|
|
632
512
|
}
|
|
@@ -726,7 +606,7 @@ export class FilesPage {
|
|
|
726
606
|
}
|
|
727
607
|
|
|
728
608
|
protected renderBreadcrumbs(): TemplateResult {
|
|
729
|
-
const { breadcrumbs=[] } = this;
|
|
609
|
+
const { breadcrumbs = [] } = this.fs;
|
|
730
610
|
if (!breadcrumbs.length) {
|
|
731
611
|
return html``;
|
|
732
612
|
}
|
|
@@ -868,17 +748,10 @@ export class FilesPage {
|
|
|
868
748
|
}
|
|
869
749
|
|
|
870
750
|
protected nameCell(item: IFile): TemplateResult {
|
|
871
|
-
|
|
872
|
-
switch (item.kind) {
|
|
873
|
-
case DataFileKind: icon = 'schema'; break;
|
|
874
|
-
case FolderKind: icon = 'folder'; break;
|
|
875
|
-
case ProjectKind: icon = 'collectionsBookmark'; break;
|
|
876
|
-
default:
|
|
877
|
-
}
|
|
878
|
-
|
|
751
|
+
const icon = fileIcon(item);
|
|
879
752
|
return this.dt.cell(html`
|
|
880
753
|
${icon ? html`<ui-icon icon="${icon}" class="cell-icon"></ui-icon>` : nothing}
|
|
881
|
-
${item.info.name || 'Unnamed
|
|
754
|
+
${item.info.name || 'Unnamed file'}
|
|
882
755
|
`, { class: 'name-cell', data: { kind: item.kind } });
|
|
883
756
|
}
|
|
884
757
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IFile } from "@api-client/core/build/browser.js";
|
|
2
2
|
import { html, TemplateResult } from "lit";
|
|
3
3
|
import type ApiClientScreen from "../ApiClient.screen.js";
|
|
4
4
|
import { FilesPage } from "./Files.page.js";
|
|
@@ -7,45 +7,7 @@ export class SharedPage extends FilesPage {
|
|
|
7
7
|
constructor(screen: ApiClientScreen) {
|
|
8
8
|
super(screen);
|
|
9
9
|
this.dt.options.ariaLabel = 'Shared with you';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
protected override async listFiles(opts: ContextSpaceListOptions): Promise<ContextListResult<IFile>> {
|
|
13
|
-
return this.screen.bindings.store.shared.list(opts);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
override handleFileEvent(event: BroadcastEvent): void {
|
|
17
|
-
const typed = event as BroadcastEvent & BroadcastFileData;
|
|
18
|
-
if (typed.parent && typed.parent !== this.folder) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
// if (typed.alt !== 'meta') {
|
|
22
|
-
// return;
|
|
23
|
-
// }
|
|
24
|
-
switch (event.operation) {
|
|
25
|
-
case 'access-granted': this.handleFileAccessGranted(typed as FileAccessBroadcastEvent); break;
|
|
26
|
-
case 'access-removed': this.handleFileAccessRemoved(typed as FileAccessBroadcastEvent); break;
|
|
27
|
-
default: super.handleFileEvent(event);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async handleFileAccessGranted(event: FileAccessBroadcastEvent): Promise<void> {
|
|
32
|
-
const { key } = event;
|
|
33
|
-
try {
|
|
34
|
-
const file = await this.screen.bindings.store.files.read(key);
|
|
35
|
-
const index = this.files.findIndex(i => i.key === key);
|
|
36
|
-
if (index >= 0) {
|
|
37
|
-
this.files.splice(index, 1);
|
|
38
|
-
}
|
|
39
|
-
this.handleFileCreated(file);
|
|
40
|
-
} catch (e) {
|
|
41
|
-
// eslint-disable-next-line no-console
|
|
42
|
-
console.warn(e);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
handleFileAccessRemoved(event: FileAccessBroadcastEvent): void {
|
|
47
|
-
const { key } = event;
|
|
48
|
-
this.removeFileFromList(key);
|
|
10
|
+
this.fs.source = 'shared';
|
|
49
11
|
}
|
|
50
12
|
|
|
51
13
|
override render(): TemplateResult {
|