@api-client/ui 0.0.10 → 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 +16 -1
- 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/index.html +3 -0
- package/demo/layout/index.html +91 -0
- package/demo/layout/index.ts +182 -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/layout/SplitItem.d.ts +1 -9
- package/dist/elements/layout/SplitItem.d.ts.map +1 -1
- package/dist/elements/layout/SplitItem.js +27 -20
- package/dist/elements/layout/SplitItem.js.map +1 -1
- package/dist/elements/layout/SplitLayout.d.ts +16 -14
- package/dist/elements/layout/SplitLayout.d.ts.map +1 -1
- package/dist/elements/layout/SplitLayout.js +47 -42
- package/dist/elements/layout/SplitLayout.js.map +1 -1
- package/dist/elements/layout/SplitPanel.d.ts +7 -2
- package/dist/elements/layout/SplitPanel.d.ts.map +1 -1
- package/dist/elements/layout/SplitPanel.js +130 -52
- package/dist/elements/layout/SplitPanel.js.map +1 -1
- package/dist/elements/layout/SplitView.d.ts.map +1 -1
- package/dist/elements/layout/SplitView.js +18 -14
- package/dist/elements/layout/SplitView.js.map +1 -1
- package/dist/elements/layout/type.d.ts +3 -3
- package/dist/elements/layout/type.d.ts.map +1 -1
- package/dist/elements/layout/type.js.map +1 -1
- 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/pages/http-project/HttpClientCommands.d.ts.map +1 -1
- package/dist/pages/http-project/HttpClientCommands.js +28 -12
- package/dist/pages/http-project/HttpClientCommands.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 +2 -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/layout/SplitItem.ts +29 -21
- package/src/elements/layout/SplitLayout.ts +53 -43
- package/src/elements/layout/SplitPanel.ts +140 -57
- package/src/elements/layout/SplitView.ts +18 -15
- package/src/elements/layout/type.ts +3 -4
- 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/pages/http-project/HttpClientCommands.ts +28 -12
- 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/layout/SplitItem.test.ts +76 -75
- package/test/elements/layout/SplitLayoutManager.test.ts +70 -69
- package/test/elements/layout/SplitPanel.test.ts +10 -7
- 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 +10 -0
- package/web-test-runner.config.mjs +51 -2
- package/dist/define/files/share-file.d.ts.map +0 -1
- package/dist/define/files/share-file.js.map +0 -1
- package/dist/define/layout/layout-panel.d.ts +0 -7
- package/dist/define/layout/layout-panel.d.ts.map +0 -1
- package/dist/define/layout/layout-panel.js +0 -3
- package/dist/define/layout/layout-panel.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/layout/LayoutManager.d.ts +0 -327
- package/dist/elements/layout/LayoutManager.d.ts.map +0 -1
- package/dist/elements/layout/LayoutManager.js +0 -747
- package/dist/elements/layout/LayoutManager.js.map +0 -1
- package/dist/elements/layout/LayoutPanelElement.d.ts +0 -62
- package/dist/elements/layout/LayoutPanelElement.d.ts.map +0 -1
- package/dist/elements/layout/LayoutPanelElement.js +0 -628
- package/dist/elements/layout/LayoutPanelElement.js.map +0 -1
- package/src/define/layout/layout-panel.ts +0 -9
- package/src/elements/layout/LayoutManager.ts +0 -930
- package/src/elements/layout/LayoutPanelElement.ts +0 -651
- /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
|
@@ -8,6 +8,7 @@ import CheckboxElement from '../../ui/input/CheckboxElement.js';
|
|
|
8
8
|
import typography from "../../styles/m3/typography.module.js";
|
|
9
9
|
import surface from "../../styles/m3/surface.module.js";
|
|
10
10
|
import '../../define/ui/ui-icon.js';
|
|
11
|
+
import '../../define/ui/ui-button.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* A base class for demo pages in the API Client ecosystem.
|
|
@@ -33,6 +34,11 @@ export abstract class DemoPage extends RouteMixin(RenderableMixin(EventTarget))
|
|
|
33
34
|
*/
|
|
34
35
|
@reactive() darkThemeActive = false;
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* For some demo pages, whether the user is authenticated in the store.
|
|
39
|
+
*/
|
|
40
|
+
authenticated = false;
|
|
41
|
+
|
|
36
42
|
constructor() {
|
|
37
43
|
super();
|
|
38
44
|
this.handleMediaQuery = this.handleMediaQuery.bind(this);
|
|
@@ -151,4 +157,15 @@ export abstract class DemoPage extends RouteMixin(RenderableMixin(EventTarget))
|
|
|
151
157
|
* ```
|
|
152
158
|
*/
|
|
153
159
|
abstract contentTemplate(): TemplateResult;
|
|
160
|
+
|
|
161
|
+
handleAuthenticate(): void {
|
|
162
|
+
// ...
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
authenticateTemplate(): TemplateResult {
|
|
166
|
+
return html`
|
|
167
|
+
<p>Store authorization required.</p>
|
|
168
|
+
<ui-button @click="${this.handleAuthenticate}">Authenticate</ui-button>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
154
171
|
}
|
|
@@ -85,12 +85,16 @@ const commands: IContextMenuCommand<HttpProjectContextualStore>[] = [
|
|
|
85
85
|
label: 'Close',
|
|
86
86
|
icon: close,
|
|
87
87
|
execute: (init): void => {
|
|
88
|
-
const { key } = init.target.dataset;
|
|
89
|
-
if (!key) {
|
|
88
|
+
const { key, panel } = init.target.dataset;
|
|
89
|
+
if (!key || !panel) {
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
const layout = init.store.get('layout');
|
|
93
|
-
layout.
|
|
93
|
+
const parent = layout.findPanel(panel);
|
|
94
|
+
if (!parent) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
parent.removeItem(key);
|
|
94
98
|
}
|
|
95
99
|
},
|
|
96
100
|
{
|
|
@@ -105,12 +109,16 @@ const commands: IContextMenuCommand<HttpProjectContextualStore>[] = [
|
|
|
105
109
|
return true;
|
|
106
110
|
},
|
|
107
111
|
execute: (init): void => {
|
|
108
|
-
const { key } = init.target.dataset;
|
|
109
|
-
if (!key) {
|
|
112
|
+
const { key, panel } = init.target.dataset;
|
|
113
|
+
if (!key || !panel) {
|
|
110
114
|
return;
|
|
111
115
|
}
|
|
112
116
|
const layout = init.store.get('layout');
|
|
113
|
-
layout.
|
|
117
|
+
const parent = layout.findPanel(panel);
|
|
118
|
+
if (!parent) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
parent.removeRelative(key, SplitCloseDirection.both);
|
|
114
122
|
},
|
|
115
123
|
},
|
|
116
124
|
{
|
|
@@ -124,12 +132,16 @@ const commands: IContextMenuCommand<HttpProjectContextualStore>[] = [
|
|
|
124
132
|
return true;
|
|
125
133
|
},
|
|
126
134
|
execute: (init): void => {
|
|
127
|
-
const { key } = init.target.dataset;
|
|
128
|
-
if (!key) {
|
|
135
|
+
const { key, panel } = init.target.dataset;
|
|
136
|
+
if (!key || !panel) {
|
|
129
137
|
return;
|
|
130
138
|
}
|
|
131
139
|
const layout = init.store.get('layout');
|
|
132
|
-
layout.
|
|
140
|
+
const parent = layout.findPanel(panel);
|
|
141
|
+
if (!parent) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
parent.removeRelative(key, SplitCloseDirection.right);
|
|
133
145
|
},
|
|
134
146
|
},
|
|
135
147
|
{
|
|
@@ -143,12 +155,16 @@ const commands: IContextMenuCommand<HttpProjectContextualStore>[] = [
|
|
|
143
155
|
return true;
|
|
144
156
|
},
|
|
145
157
|
execute: (init): void => {
|
|
146
|
-
const { key } = init.target.dataset;
|
|
147
|
-
if (!key) {
|
|
158
|
+
const { key, panel } = init.target.dataset;
|
|
159
|
+
if (!key || !panel) {
|
|
148
160
|
return;
|
|
149
161
|
}
|
|
150
162
|
const layout = init.store.get('layout');
|
|
151
|
-
layout.
|
|
163
|
+
const parent = layout.findPanel(panel);
|
|
164
|
+
if (!parent) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
parent.removeRelative(key, SplitCloseDirection.left);
|
|
152
168
|
},
|
|
153
169
|
},
|
|
154
170
|
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BroadcastEvent,
|
|
3
|
+
BroadcastFileData,
|
|
4
|
+
ContextListResult,
|
|
5
|
+
ContextSpaceListOptions,
|
|
6
|
+
DeletedBroadcastEvent,
|
|
7
|
+
FileAccessBroadcastEvent,
|
|
8
|
+
FileBreadcrumb,
|
|
9
|
+
FileMetaCreatedBroadcastEvent,
|
|
10
|
+
FilePatchBroadcastEvent,
|
|
11
|
+
IFile,
|
|
12
|
+
ListFileKind,
|
|
13
|
+
} from "@api-client/core/build/browser.js";
|
|
14
|
+
import { Patch } from "@api-client/json";
|
|
15
|
+
import { filesSortFunction } from "../elements/store/FilesLib.js";
|
|
16
|
+
import { Events } from "../events/Events.js";
|
|
17
|
+
import { StoreBroadcast } from "../http-client/store/StoreBroadcast.js";
|
|
18
|
+
|
|
19
|
+
type FilesSource = 'own' | 'shared';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Contains a logic to read files from the store.
|
|
23
|
+
*
|
|
24
|
+
* It uses app events to query for the data.
|
|
25
|
+
*/
|
|
26
|
+
export class FileSystem extends EventTarget {
|
|
27
|
+
source: FilesSource = 'own';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The list of files to render.
|
|
31
|
+
* Do not overwrite this property or when you must then update the DataTable's `items` as well.
|
|
32
|
+
*/
|
|
33
|
+
files: IFile[] = [];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The pagination cursor for files.
|
|
37
|
+
*/
|
|
38
|
+
cursor?: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* This is set when the last files query did not yeld results.
|
|
42
|
+
*/
|
|
43
|
+
queryEnded?: boolean;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The currently rendered breadcrumbs.
|
|
47
|
+
*/
|
|
48
|
+
breadcrumbs?: FileBreadcrumb[];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether the UI is reading files
|
|
52
|
+
*/
|
|
53
|
+
reading?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The list of file kinds to list.
|
|
57
|
+
* Folders are always included.
|
|
58
|
+
*/
|
|
59
|
+
kinds?: ListFileKind[];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The timeout for the query debouncer.
|
|
63
|
+
* When any property change this is the time the element will wait
|
|
64
|
+
* until the actual query is made.
|
|
65
|
+
*/
|
|
66
|
+
debounceTimeout = 100;
|
|
67
|
+
|
|
68
|
+
debouncerValue?: number;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The variable parent folder key.
|
|
72
|
+
* This is set when interacting with the UI or when the `folder` attribute change.
|
|
73
|
+
*/
|
|
74
|
+
parent?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The page limit. Defaults to the store defaults.
|
|
78
|
+
* @attribute
|
|
79
|
+
*/
|
|
80
|
+
limit?: number;
|
|
81
|
+
|
|
82
|
+
filesChannel = new BroadcastChannel(StoreBroadcast.files);
|
|
83
|
+
|
|
84
|
+
eventsTarget: EventTarget = document.body;
|
|
85
|
+
|
|
86
|
+
constructor() {
|
|
87
|
+
super();
|
|
88
|
+
this.fileMetaMessageHandler = this.fileMetaMessageHandler.bind(this);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
observe(): void {
|
|
92
|
+
this.filesChannel.addEventListener('message', this.fileMetaMessageHandler);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
unobserve(): void {
|
|
96
|
+
this.filesChannel.removeEventListener('message', this.fileMetaMessageHandler);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fileMetaMessageHandler(e: MessageEvent): void {
|
|
100
|
+
const event = e.data as BroadcastEvent & BroadcastFileData;
|
|
101
|
+
if (event.parent !== this.parent) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (event.alt !== 'meta') {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
switch (event.operation) {
|
|
108
|
+
case 'created': this.handleFileCreated((event as FileMetaCreatedBroadcastEvent).data); break;
|
|
109
|
+
case 'deleted': this.handleFileDeleted(event as DeletedBroadcastEvent); break;
|
|
110
|
+
case 'patch': this.handleFilePatch(event as FilePatchBroadcastEvent); break;
|
|
111
|
+
case 'access-granted': this.handleFileAccessGranted(event as FileAccessBroadcastEvent); break;
|
|
112
|
+
case 'access-removed': this.handleFileAccessRemoved(event as FileAccessBroadcastEvent); break;
|
|
113
|
+
default:
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
handleFileCreated(file: IFile): void {
|
|
118
|
+
this.files.push(file);
|
|
119
|
+
// We don't sort here so files that appear at the end which makes sense because the list is scrolling.
|
|
120
|
+
// this.files.sort(filesSortFunction);
|
|
121
|
+
this.notifyUpdate();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
handleFileDeleted(event: DeletedBroadcastEvent): void {
|
|
125
|
+
this.removeFileFromList(event.key);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async handleFileAccessGranted(event: FileAccessBroadcastEvent): Promise<void> {
|
|
129
|
+
if (this.source !== 'shared') {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const { key } = event;
|
|
133
|
+
try {
|
|
134
|
+
const file = await Events.Store.File.read(key, false, this.eventsTarget);
|
|
135
|
+
const index = this.files.findIndex(i => i.key === key);
|
|
136
|
+
if (index >= 0) {
|
|
137
|
+
this.files.splice(index, 1);
|
|
138
|
+
}
|
|
139
|
+
this.handleFileCreated(file);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// eslint-disable-next-line no-console
|
|
142
|
+
console.warn(e);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
handleFileAccessRemoved(event: FileAccessBroadcastEvent): void {
|
|
147
|
+
if (this.source !== 'shared') {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const { key } = event;
|
|
151
|
+
this.removeFileFromList(key);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
removeFileFromList(key: string): void {
|
|
155
|
+
const index = this.files.findIndex(i => i.key === key);
|
|
156
|
+
if (index === -1) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
this.files.splice(index, 1);
|
|
160
|
+
this.notifyUpdate();
|
|
161
|
+
this.dispatchEvent(new CustomEvent('delete', { detail: key }));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
handleFilePatch(event: FilePatchBroadcastEvent): void {
|
|
165
|
+
const { data, key } = event;
|
|
166
|
+
const index = this.files.findIndex(i => i.key === key);
|
|
167
|
+
if (index === -1) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const file = this.files[index];
|
|
171
|
+
const result = Patch.apply(file, data.patch);
|
|
172
|
+
this.files[index] = result.doc as IFile;
|
|
173
|
+
this.notifyUpdate();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
debounceQuery(): void {
|
|
177
|
+
if (this.debouncerValue) {
|
|
178
|
+
clearTimeout(this.debouncerValue);
|
|
179
|
+
}
|
|
180
|
+
this.debouncerValue = window.setTimeout(() => {
|
|
181
|
+
this.queryPage();
|
|
182
|
+
this.queryBreadcrumbs();
|
|
183
|
+
}, this.debounceTimeout);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
resetList(): void {
|
|
187
|
+
this.files = [];
|
|
188
|
+
this.cursor = undefined;
|
|
189
|
+
this.breadcrumbs = undefined;
|
|
190
|
+
this.queryEnded = false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async queryPage(): Promise<void> {
|
|
194
|
+
if (this.reading || this.queryEnded) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this.reading = true;
|
|
198
|
+
this.notifyUpdate();
|
|
199
|
+
try {
|
|
200
|
+
const result = await this.listFiles();
|
|
201
|
+
if (!result) {
|
|
202
|
+
throw new Error(`Files event not handled.`);
|
|
203
|
+
}
|
|
204
|
+
const { items, cursor } = result;
|
|
205
|
+
if (!items.length) {
|
|
206
|
+
this.queryEnded = true;
|
|
207
|
+
} else {
|
|
208
|
+
if (!this.files.length) {
|
|
209
|
+
// we only sort the first page as later the user scrolls and sorting would change the order.
|
|
210
|
+
items.sort(filesSortFunction);
|
|
211
|
+
}
|
|
212
|
+
this.files = this.files.concat(items);
|
|
213
|
+
}
|
|
214
|
+
if (cursor) {
|
|
215
|
+
this.cursor = cursor;
|
|
216
|
+
}
|
|
217
|
+
} catch(e) {
|
|
218
|
+
const err = e as Error;
|
|
219
|
+
this.notifyError(`Unable to load files list. ${err.message}`);
|
|
220
|
+
// eslint-disable-next-line no-console
|
|
221
|
+
console.error(e);
|
|
222
|
+
} finally {
|
|
223
|
+
this.reading = false;
|
|
224
|
+
}
|
|
225
|
+
this.dispatchEvent(new Event('querycomplete'));
|
|
226
|
+
this.notifyUpdate();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async queryBreadcrumbs(): Promise<void> {
|
|
230
|
+
if (this.parent) {
|
|
231
|
+
const br = await this.listBreadcrumbs(this.parent);
|
|
232
|
+
this.breadcrumbs = br.items;
|
|
233
|
+
} else {
|
|
234
|
+
this.breadcrumbs = undefined;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
getPageQueryOptions(): ContextSpaceListOptions {
|
|
239
|
+
const { cursor: filesCursor, parent, limit } = this;
|
|
240
|
+
const opts: ContextSpaceListOptions = {
|
|
241
|
+
space: '', // this is filled by the bindings.
|
|
242
|
+
descending: true,
|
|
243
|
+
};
|
|
244
|
+
if (filesCursor) {
|
|
245
|
+
opts.cursor = filesCursor;
|
|
246
|
+
}
|
|
247
|
+
if (parent) {
|
|
248
|
+
opts.parent = parent;
|
|
249
|
+
}
|
|
250
|
+
if (typeof limit === 'number' && limit) {
|
|
251
|
+
opts.limit = limit;
|
|
252
|
+
}
|
|
253
|
+
return opts;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async listFiles(): Promise<ContextListResult<IFile>> {
|
|
257
|
+
const { kinds } = this;
|
|
258
|
+
const opts = this.getPageQueryOptions();
|
|
259
|
+
if (this.source === 'shared') {
|
|
260
|
+
return Events.Store.File.listShared(kinds, opts, this.eventsTarget);
|
|
261
|
+
}
|
|
262
|
+
return Events.Store.File.list(kinds, opts, this.eventsTarget);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async listBreadcrumbs(folder: string): Promise<ContextListResult<FileBreadcrumb>> {
|
|
266
|
+
return Events.Store.File.breadcrumbs(folder, this.eventsTarget);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
notifyError(error: string): void {
|
|
270
|
+
this.dispatchEvent(new CustomEvent<string>('error', {
|
|
271
|
+
detail: error,
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
notifyUpdate(): void {
|
|
276
|
+
this.dispatchEvent(new Event('change'));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Sets a parent and makes the query.
|
|
281
|
+
* @param key The key of the parent to query. Empty for root folder.
|
|
282
|
+
*/
|
|
283
|
+
selectFolder(key?: string): void {
|
|
284
|
+
this.parent = key;
|
|
285
|
+
this.resetList();
|
|
286
|
+
this.debounceQuery();
|
|
287
|
+
// this.queryPage();
|
|
288
|
+
this.queryBreadcrumbs();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Moves to a parent folder, if any.
|
|
293
|
+
*/
|
|
294
|
+
async parentUp(): Promise<void> {
|
|
295
|
+
const { breadcrumbs = [] } = this;
|
|
296
|
+
const [, parent] = breadcrumbs;
|
|
297
|
+
if (!parent) {
|
|
298
|
+
if (!this.parent) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
this.parent = undefined;
|
|
302
|
+
} else {
|
|
303
|
+
if (this.parent === parent.key) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
this.parent = parent.key;
|
|
307
|
+
}
|
|
308
|
+
this.resetList();
|
|
309
|
+
await Promise.all([
|
|
310
|
+
this.queryPage(),
|
|
311
|
+
this.queryBreadcrumbs(),
|
|
312
|
+
]);
|
|
313
|
+
this.notifyUpdate();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @param target The HTML element that is a scrollable list if files.
|
|
318
|
+
* @returns True when the scroll position indicates that the user scrolled near the end of the list.
|
|
319
|
+
*/
|
|
320
|
+
isListEnd(target: HTMLElement): boolean {
|
|
321
|
+
const { scrollTop, offsetHeight, scrollHeight } = target;
|
|
322
|
+
// 20 is the offset which qualifies as the end. An arbitrary number.
|
|
323
|
+
return scrollTop + offsetHeight >= scrollHeight - 20;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -65,6 +65,13 @@ body {
|
|
|
65
65
|
justify-content: center;
|
|
66
66
|
position: absolute;
|
|
67
67
|
inset: 0;
|
|
68
|
+
background-color: var(--md-sys-color-surface);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.message,
|
|
72
|
+
.sub-message,
|
|
73
|
+
.auth-required-screen {
|
|
74
|
+
color: var(--md-sys-color-on-surface);
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
.full-error {
|
package/src/ui/icons/Icons.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { svg, SVGTemplateResult } from 'lit';
|
|
|
8
8
|
export const iconWrapper = (tpl: SVGTemplateResult, width = 24, height = 24, top = 0, left = 0): SVGTemplateResult => svg`<svg viewBox="${top} ${left} ${width} ${height}" preserveAspectRatio="xMidYMid meet" focusable="false" style="pointer-events: none; display: block; width: 100%; height: 100%;">${tpl}</svg>`;
|
|
9
9
|
|
|
10
10
|
export type IconType =
|
|
11
|
-
'add' | 'api' | 'apps' | 'arrowBack' | 'arrowDropDown' | 'cancel' | 'cancelFilled' | 'check' | 'checkBox' | 'checkBoxBlank' | 'checkIndeterminate' | 'chevronLeft' | 'chevronRight' | 'close' | 'cloud' | 'cloudFilled' | 'collectionsBookmark' | 'deleteFile' | 'deleteOutline' | 'edit' | 'environment' | 'expandMore' |
|
|
11
|
+
'add' | 'api' | 'apps' | 'arrowBack' | 'arrowDropDown' | 'cancel' | 'cancelFilled' | 'certificate' | 'check' | 'checkBox' | 'checkBoxBlank' | 'checkIndeterminate' | 'chevronLeft' | 'chevronRight' | 'close' | 'cloud' | 'cloudFilled' | 'collectionsBookmark' | 'deleteFile' | 'deleteOutline' | 'edit' | 'environment' | 'expandMore' |
|
|
12
12
|
'fileDownload' | 'folder' | 'folderFilled' | 'folderShared' | 'help' | 'history' | 'info' | 'key' | 'menu' | 'leaderBoard' |
|
|
13
13
|
'moreVert' | 'openInNew' | 'personAdd' | 'playArrow' | 'project' | 'remove' | 'rename' | 'request' | 'restoreFromTrash' | 'save' |
|
|
14
14
|
'schema' | 'schemaEntity' | 'schemaModel' | 'schemaNamespace' | 'search' | 'send' | 'settings' | 'space' | 'taskAlt' | 'timeline' | 'tune' | 'viewGrid' | 'viewList' | 'visibility' | 'visibilityOff' | 'warning';
|
|
@@ -20,6 +20,7 @@ export const arrowBack = iconWrapper(svg`<path d="M20 11H7.83l5.59-5.59L12 4l-8
|
|
|
20
20
|
export const arrowDropDown = iconWrapper(svg`<path d="M7 10l5 5 5-5z"/>`);
|
|
21
21
|
export const cancel = iconWrapper(svg`<path d="M0 0h24v24H0V0z" fill="none" opacity=".87"/><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3.59-13L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41z"/>`);
|
|
22
22
|
export const cancelFilled = iconWrapper(svg`<path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/>`);
|
|
23
|
+
export const certificate = iconWrapper(svg`<path d="M4 3c-1.11 0-2 .89-2 2v10a2 2 0 0 0 2 2h8v5l3-3l3 3v-5h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H4m8 2l3 2l3-2v3.5l3 1.5l-3 1.5V15l-3-2l-3 2v-3.5L9 10l3-1.5V5M4 5h5v2H4V5m0 4h3v2H4V9m0 4h5v2H4v-2Z"/>`);
|
|
23
24
|
export const check = iconWrapper(svg`<path d="m9.55 18-5.7-5.7 1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4Z"/>`);
|
|
24
25
|
export const checkBox = iconWrapper(svg`<path d="m10.6 16.2 7.05-7.05-1.4-1.4-5.65 5.65-2.85-2.85-1.4 1.4ZM5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14ZM5 5v14V5Z"/>`);
|
|
25
26
|
export const checkBoxBlank = iconWrapper(svg`<path d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Z"/>`);
|
|
@@ -27,7 +27,7 @@ export interface UiDropdownListSelection {
|
|
|
27
27
|
*
|
|
28
28
|
* @slot - The default slot for the dropdown trigger (button)
|
|
29
29
|
* @slot dropdown - The slot for the list.
|
|
30
|
-
* @fires select - Custom event with the selected item on the `detail.item` when the user selected an item.
|
|
30
|
+
* @fires select - Custom event with the selected item on the `detail.item` when the user selected an item. When the event is cancelled then there's no side effects (closing the dropdown)
|
|
31
31
|
* @fires dropdownopen - An event informing other dropdowns that this one was opened and other should close.
|
|
32
32
|
* @fires open - An event dispatched when the open state change through a user interaction
|
|
33
33
|
*/
|
|
@@ -91,6 +91,13 @@ export default class UiDropdownList extends LitElement {
|
|
|
91
91
|
*/
|
|
92
92
|
@property({ type: Boolean }) matchTriggerWidth?: boolean;
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* When set it closes the drop-down when `tab` button is pressed.
|
|
96
|
+
* This is not a default behavior since the drop-down content can have its own logic
|
|
97
|
+
* related to tab index.
|
|
98
|
+
*/
|
|
99
|
+
@property({ type: Boolean }) closeOnTab?: boolean;
|
|
100
|
+
|
|
94
101
|
/**
|
|
95
102
|
* The first element located in the default slot.
|
|
96
103
|
*/
|
|
@@ -145,9 +152,7 @@ export default class UiDropdownList extends LitElement {
|
|
|
145
152
|
if (e.composedPath()[0] === this) {
|
|
146
153
|
return;
|
|
147
154
|
}
|
|
148
|
-
this.
|
|
149
|
-
this.updateExpanded();
|
|
150
|
-
this.notifyOpen();
|
|
155
|
+
this.close();
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
protected updateExpanded(): void {
|
|
@@ -212,10 +217,15 @@ export default class UiDropdownList extends LitElement {
|
|
|
212
217
|
}
|
|
213
218
|
|
|
214
219
|
protected contentKeyDownHandler(e: KeyboardEvent): void {
|
|
215
|
-
if (e.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
220
|
+
if (e.defaultPrevented) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (e.code === 'Escape') {
|
|
224
|
+
this.close();
|
|
225
|
+
} else if (e.code === 'Tab') {
|
|
226
|
+
if (this.closeOnTab) {
|
|
227
|
+
this.close();
|
|
228
|
+
}
|
|
219
229
|
} else if (['Enter', 'Space'].includes(e.code)) {
|
|
220
230
|
this.activate(e);
|
|
221
231
|
}
|
|
@@ -225,6 +235,17 @@ export default class UiDropdownList extends LitElement {
|
|
|
225
235
|
this.activate(e);
|
|
226
236
|
}
|
|
227
237
|
|
|
238
|
+
close(): void {
|
|
239
|
+
this.open = false;
|
|
240
|
+
this.updateExpanded();
|
|
241
|
+
this.notifyOpen();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
protected contentCloseHandler(e: Event): void {
|
|
245
|
+
e.stopPropagation();
|
|
246
|
+
this.close();
|
|
247
|
+
}
|
|
248
|
+
|
|
228
249
|
protected override willUpdate(cp: PropertyValues<this>): void {
|
|
229
250
|
super.willUpdate(cp);
|
|
230
251
|
if ((cp.has('noOverlap') || cp.has('verticalAlign') || cp.has('horizontalAlign') || cp.has('open')) && this.open) {
|
|
@@ -272,9 +293,7 @@ export default class UiDropdownList extends LitElement {
|
|
|
272
293
|
if (inside) {
|
|
273
294
|
return;
|
|
274
295
|
}
|
|
275
|
-
this.
|
|
276
|
-
this.updateExpanded();
|
|
277
|
-
this.notifyOpen();
|
|
296
|
+
this.close();
|
|
278
297
|
}
|
|
279
298
|
|
|
280
299
|
protected toggleOpened(): void {
|
|
@@ -311,6 +330,9 @@ export default class UiDropdownList extends LitElement {
|
|
|
311
330
|
}
|
|
312
331
|
|
|
313
332
|
protected activate(e: Event): void {
|
|
333
|
+
if (e.defaultPrevented) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
314
336
|
const path = e.composedPath();
|
|
315
337
|
let item: HTMLElement | undefined;
|
|
316
338
|
while (!item) {
|
|
@@ -328,14 +350,18 @@ export default class UiDropdownList extends LitElement {
|
|
|
328
350
|
if (!item) {
|
|
329
351
|
return;
|
|
330
352
|
}
|
|
331
|
-
|
|
353
|
+
const event = new CustomEvent<UiDropdownListSelection>('select', {
|
|
354
|
+
cancelable: true,
|
|
355
|
+
composed: true,
|
|
332
356
|
detail: {
|
|
333
357
|
item,
|
|
334
|
-
}
|
|
335
|
-
})
|
|
336
|
-
this.
|
|
337
|
-
|
|
338
|
-
|
|
358
|
+
},
|
|
359
|
+
})
|
|
360
|
+
this.dispatchEvent(event);
|
|
361
|
+
if (event.defaultPrevented) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
this.close();
|
|
339
365
|
}
|
|
340
366
|
|
|
341
367
|
protected notifyOpen(): void {
|
|
@@ -385,6 +411,7 @@ export default class UiDropdownList extends LitElement {
|
|
|
385
411
|
@slotchange="${this.dropdownChanged}"
|
|
386
412
|
@keydown="${this.contentKeyDownHandler}"
|
|
387
413
|
@click="${this.contentClickHandler}"
|
|
414
|
+
@close="${this.contentCloseHandler}"
|
|
388
415
|
></slot>
|
|
389
416
|
</div>
|
|
390
417
|
`;
|
package/src/ui/list/UiList.ts
CHANGED
|
@@ -58,6 +58,15 @@ export default class UiList extends LitElement {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
override focus(options?: FocusOptions): void {
|
|
62
|
+
const { activeListItem } = this;
|
|
63
|
+
if (activeListItem) {
|
|
64
|
+
activeListItem.focus(options);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.activateFirstItem();
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
override firstUpdated(changedProperties: PropertyValues): void {
|
|
62
71
|
super.firstUpdated(changedProperties);
|
|
63
72
|
|
|
@@ -66,7 +75,7 @@ export default class UiList extends LitElement {
|
|
|
66
75
|
|
|
67
76
|
activateFirstItem(): void {
|
|
68
77
|
this.activeListItem = this.getFirstItem();
|
|
69
|
-
this.activeListItem
|
|
78
|
+
this.activeListItem?.activate();
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
activateLastItem(): void {
|
|
@@ -318,30 +327,37 @@ export default class UiList extends LitElement {
|
|
|
318
327
|
this.manageSelection(item);
|
|
319
328
|
item.activate();
|
|
320
329
|
this.activeListItem = item;
|
|
321
|
-
this.notifySelect(item)
|
|
330
|
+
if (this.notifySelect(item)) {
|
|
331
|
+
e.preventDefault();
|
|
332
|
+
}
|
|
322
333
|
}
|
|
323
334
|
|
|
324
|
-
|
|
335
|
+
/**
|
|
336
|
+
* @param item The UiListItem that is selected.
|
|
337
|
+
* @returns True when the event was canceled.
|
|
338
|
+
*/
|
|
339
|
+
notifySelect(item: UiListItem): boolean {
|
|
325
340
|
const index = this.items.indexOf(item);
|
|
326
341
|
if (index === -1) {
|
|
327
|
-
return;
|
|
342
|
+
return false;
|
|
328
343
|
}
|
|
329
|
-
|
|
344
|
+
const event = new CustomEvent<UiListSelection>('select', {
|
|
345
|
+
cancelable: true,
|
|
330
346
|
detail: {
|
|
331
347
|
item,
|
|
332
348
|
index,
|
|
333
349
|
}
|
|
334
|
-
})
|
|
350
|
+
});
|
|
351
|
+
this.dispatchEvent(event);
|
|
352
|
+
return event.defaultPrevented;
|
|
335
353
|
}
|
|
336
354
|
|
|
337
355
|
protected manageSelection(item: UiListItem): void {
|
|
338
356
|
if (!this.selectActive) {
|
|
339
357
|
return;
|
|
340
358
|
}
|
|
341
|
-
const {
|
|
342
|
-
|
|
343
|
-
activeListItem.classList.remove('select');
|
|
344
|
-
}
|
|
359
|
+
const { items } = this;
|
|
360
|
+
items.forEach((current) => current.classList.remove('select'));
|
|
345
361
|
item.classList.add('select');
|
|
346
362
|
}
|
|
347
363
|
|