@milaboratories/uikit 2.2.16 → 2.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/pl-uikit.js +3218 -2989
  3. package/dist/pl-uikit.umd.cjs +6 -6
  4. package/dist/src/components/ContextProvider.vue.d.ts +7 -8
  5. package/dist/src/components/DataTable/AddColumnBtn.vue.d.ts +1 -1
  6. package/dist/src/components/DataTable/BaseCellComponent.vue.d.ts +5 -8
  7. package/dist/src/components/DataTable/ColumnCaret.vue.d.ts +3 -4
  8. package/dist/src/components/DataTable/TScroll.vue.d.ts +5 -8
  9. package/dist/src/components/DataTable/TableComponent.vue.d.ts +5 -6
  10. package/dist/src/components/DataTable/TdCell.vue.d.ts +3 -4
  11. package/dist/src/components/DataTable/ThCell.vue.d.ts +5 -6
  12. package/dist/src/components/DataTable/TrBody.vue.d.ts +6 -6
  13. package/dist/src/components/DataTable/TrHead.vue.d.ts +3 -2
  14. package/dist/src/components/DataTable/assets/TableIcon.vue.d.ts +1 -1
  15. package/dist/src/components/DropdownListItem.vue.d.ts +5 -10
  16. package/dist/src/components/GridTable/AddColumnBtn.vue.d.ts +1 -1
  17. package/dist/src/components/GridTable/TRow.vue.d.ts +8 -10
  18. package/dist/src/components/GridTable/TdCell.vue.d.ts +10 -12
  19. package/dist/src/components/GridTable/ThCell.vue.d.ts +5 -8
  20. package/dist/src/components/GridTable/assets/TableIcon.vue.d.ts +1 -1
  21. package/dist/src/components/GridTable/index.vue.d.ts +8 -8
  22. package/dist/src/components/HScroll.vue.d.ts +4 -7
  23. package/dist/src/components/InputRange.vue.d.ts +4 -6
  24. package/dist/src/components/LongText.vue.d.ts +3 -2
  25. package/dist/src/components/PlAccordion/ExpandTransition.vue.d.ts +2 -1
  26. package/dist/src/components/PlAccordion/PlAccordion.vue.d.ts +6 -5
  27. package/dist/src/components/PlAccordion/PlAccordionSection.vue.d.ts +6 -5
  28. package/dist/src/components/PlBtnGhost/PlBtnGhost.vue.d.ts +1 -1
  29. package/dist/src/components/PlBtnGroup/PlBtnGroup.vue.d.ts +1 -1
  30. package/dist/src/components/PlBtnLink/PlBtnLink.vue.d.ts +12 -35
  31. package/dist/src/components/PlBtnSplit/PlBtnSplit.vue.d.ts +3 -4
  32. package/dist/src/components/PlCheckbox/PlCheckboxBase.vue.d.ts +5 -7
  33. package/dist/src/components/PlCheckboxGroup/PlCheckboxGroup.vue.d.ts +1 -1
  34. package/dist/src/components/PlChip/PlChip.vue.d.ts +8 -9
  35. package/dist/src/components/PlDropdown/PlDropdown.vue.d.ts +1 -1
  36. package/dist/src/components/PlDropdownLegacy/PlDropdownLegacy.vue.d.ts +1 -1
  37. package/dist/src/components/PlDropdownLine/PlDropdownLine.vue.d.ts +16 -23
  38. package/dist/src/components/PlDropdownLine/ResizableInput.vue.d.ts +5 -11
  39. package/dist/src/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts +1 -1
  40. package/dist/src/components/PlDropdownRef/PlDropdownRef.vue.d.ts +1 -1
  41. package/dist/src/components/PlEditableTitle/PlEditableTitle.vue.d.ts +5 -5
  42. package/dist/src/components/PlFileDialog/Local.vue.d.ts +8 -0
  43. package/dist/src/components/PlFileDialog/PlFileDialog.vue.d.ts +84 -12
  44. package/dist/src/components/PlFileDialog/Remote.vue.d.ts +28 -0
  45. package/dist/src/components/PlFileDialog/remote.d.ts +20 -0
  46. package/dist/src/components/PlFileDialog/utils.d.ts +1 -0
  47. package/dist/src/components/PlFileInput/PlFileInput.vue.d.ts +18 -65
  48. package/dist/src/components/PlIcon16/PlIcon16.vue.d.ts +3 -5
  49. package/dist/src/components/PlIcon24/PlIcon24.vue.d.ts +3 -5
  50. package/dist/src/components/PlLogView/PlLogView.vue.d.ts +1 -1
  51. package/dist/src/components/PlMaskIcon16/PlMaskIcon16.vue.d.ts +3 -5
  52. package/dist/src/components/PlMaskIcon24/PlMaskIcon24.vue.d.ts +3 -5
  53. package/dist/src/components/PlNotificationAlert/PlNotificationAlert.vue.d.ts +6 -5
  54. package/dist/src/components/PlNumberField/PlNumberField.vue.d.ts +3 -2
  55. package/dist/src/components/PlProgressBar/PlProgressBar.vue.d.ts +3 -6
  56. package/dist/src/components/PlSearchField/PlSearchField.vue.d.ts +12 -0
  57. package/dist/src/components/PlSearchField/index.d.ts +1 -0
  58. package/dist/src/components/PlSectionSeparator/PlSectionSeparator.vue.d.ts +9 -12
  59. package/dist/src/components/PlSpacer/PlSpacer.vue.d.ts +1 -1
  60. package/dist/src/components/PlTabs/PlTabs.vue.d.ts +1 -1
  61. package/dist/src/components/PlTabs/Tab.vue.d.ts +6 -6
  62. package/dist/src/components/PlTextArea/PlTextArea.vue.d.ts +1 -1
  63. package/dist/src/components/PlTextField/PlTextField.vue.d.ts +3 -5
  64. package/dist/src/components/PlToggleSwitch/PlToggleSwitch.vue.d.ts +1 -1
  65. package/dist/src/components/PlTooltip/Beak.vue.d.ts +1 -1
  66. package/dist/src/components/Scrollable.vue.d.ts +3 -2
  67. package/dist/src/components/Slider.vue.d.ts +18 -28
  68. package/dist/src/components/SliderRange.vue.d.ts +18 -28
  69. package/dist/src/components/SliderRangeTriple.vue.d.ts +19 -29
  70. package/dist/src/components/TabItem.vue.d.ts +4 -7
  71. package/dist/src/components/ThemeSwitcher.vue.d.ts +1 -1
  72. package/dist/src/components/TransitionSlidePanel.vue.d.ts +2 -1
  73. package/dist/src/components/VScroll.vue.d.ts +4 -7
  74. package/dist/src/components/contextMenu/Menu.vue.d.ts +5 -6
  75. package/dist/src/drafts/FileBaseInput.vue.d.ts +3 -2
  76. package/dist/src/index.d.ts +1 -0
  77. package/dist/src/layout/PlBlockPage/PlBlockPage.vue.d.ts +1 -1
  78. package/dist/src/layout/PlContainer/PlContainer.vue.d.ts +1 -1
  79. package/dist/src/layout/PlGrid/PlGrid.vue.d.ts +1 -1
  80. package/dist/src/layout/PlRow/PlRow.vue.d.ts +1 -1
  81. package/dist/src/layout/PlSpacer/PlSpacer.vue.d.ts +1 -1
  82. package/dist/src/types.d.ts +1 -1
  83. package/dist/src/utils/CloseModalBtn.vue.d.ts +1 -1
  84. package/dist/style.css +1 -1
  85. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  86. package/package.json +8 -8
  87. package/src/components/PlBtnSplit/PlBtnSplit.vue +1 -2
  88. package/src/components/PlDialogModal/pl-dialog-modal.scss +0 -1
  89. package/src/components/PlFileDialog/Local.vue +95 -0
  90. package/src/components/PlFileDialog/PlFileDialog.vue +63 -310
  91. package/src/components/PlFileDialog/Remote.vue +313 -0
  92. package/src/components/PlFileDialog/pl-file-dialog.module.scss +247 -0
  93. package/src/components/PlFileDialog/remote.ts +45 -0
  94. package/src/components/PlFileDialog/utils.ts +5 -0
  95. package/src/components/PlFileInput/pl-file-input.scss +2 -5
  96. package/src/components/PlSearchField/PlSearchField.vue +85 -0
  97. package/src/components/PlSearchField/index.ts +1 -0
  98. package/src/components/PlTabs/Tab.vue +0 -1
  99. package/src/composition/useSortable.ts +4 -0
  100. package/src/index.ts +1 -0
  101. package/src/types.ts +1 -1
  102. package/src/components/PlFileDialog/pl-file-dialog.scss +0 -172
@@ -0,0 +1,313 @@
1
+ <script lang="ts" setup>
2
+ import style from './pl-file-dialog.module.scss';
3
+ import { watch, reactive, computed, toRef, onMounted } from 'vue';
4
+ import { between, notEmpty, tapIf } from '@milaboratories/helpers';
5
+ import type { StorageHandle } from '@platforma-sdk/model';
6
+ import type { ImportedFiles } from '@/types';
7
+ import { getFilePathBreadcrumbs, normalizeExtensions, type FileDialogItem } from './utils';
8
+ import { PlDropdown } from '../PlDropdown';
9
+ import { useEventListener } from '@/composition/useEventListener';
10
+ import { defaultData, useVisibleItems, vTextOverflown } from './remote';
11
+ import { PlSearchField } from '../PlSearchField';
12
+ import { PlIcon16 } from '../PlIcon16';
13
+
14
+ defineEmits<{
15
+ (e: 'update:modelValue', value: boolean): void;
16
+ (e: 'import:files', value: ImportedFiles): void;
17
+ }>();
18
+
19
+ const props = withDefaults(
20
+ defineProps<{
21
+ modelValue: boolean;
22
+ extensions?: string[]; // with dot, like ['.fastq.gz', '.fastq']
23
+ multi?: boolean;
24
+ title?: string;
25
+ autoSelectStorage?: boolean;
26
+ submit: () => void;
27
+ }>(),
28
+ {
29
+ extensions: undefined,
30
+ title: undefined,
31
+ autoSelectStorage: true,
32
+ },
33
+ );
34
+
35
+ const data = reactive(defaultData());
36
+
37
+ const resetData = () => {
38
+ data.search = '';
39
+ data.error = '';
40
+ data.lastSelected = undefined;
41
+ };
42
+
43
+ const extensions = computed(() => normalizeExtensions(props.extensions));
44
+
45
+ const visibleItems = useVisibleItems(data);
46
+
47
+ const lookup = computed(() => {
48
+ return {
49
+ modelValue: props.modelValue,
50
+ dirPath: data.dirPath,
51
+ storageHandle: data.storageEntry?.handle,
52
+ };
53
+ });
54
+
55
+ const query = (storageHandle: StorageHandle, dirPath: string) => {
56
+ if (!window.platforma) {
57
+ return;
58
+ }
59
+
60
+ if (data.currentLoadingPath === dirPath) {
61
+ return;
62
+ }
63
+
64
+ data.currentLoadingPath = dirPath;
65
+
66
+ window.platforma.lsDriver
67
+ .listFiles(storageHandle, dirPath)
68
+ .then((res) => {
69
+ if (dirPath !== data.dirPath) {
70
+ return;
71
+ }
72
+
73
+ data.items = notEmpty(res)
74
+ .entries.map((item) => ({
75
+ path: item.fullPath,
76
+ name: item.name,
77
+ isDir: item.type === 'dir',
78
+ canBeSelected: item.type === 'file' && (!extensions.value || extensions.value.some((ext) => item.fullPath.endsWith(ext))),
79
+ handle: item.type === 'file' ? item.handle : undefined,
80
+ selected: false,
81
+ }))
82
+ .sort((a, b) => {
83
+ if (a.isDir && !b.isDir) return -1;
84
+ if (!a.isDir && b.isDir) return 1;
85
+ // localeCompare for unicode alphabets
86
+ return a.name.localeCompare(b.name);
87
+ })
88
+ .map((it, id) => ({ id, ...it }));
89
+
90
+ data.lastSelected = undefined;
91
+ })
92
+ .catch((err) => (data.error = String(err)))
93
+ .finally(() => {
94
+ data.currentLoadingPath = undefined;
95
+ });
96
+ };
97
+
98
+ const load = () => {
99
+ resetData();
100
+ const { storageHandle, dirPath, modelValue } = lookup.value;
101
+ if (storageHandle && modelValue) {
102
+ query(storageHandle, dirPath);
103
+ }
104
+ };
105
+
106
+ const breadcrumbs = computed(() => getFilePathBreadcrumbs(data.dirPath));
107
+
108
+ const selectedFiles = computed(() => data.items.filter((f) => f.canBeSelected && f.selected && !f.isDir));
109
+
110
+ const isReady = computed(() => selectedFiles.value.length > 0 && data.storageEntry?.handle);
111
+
112
+ const getFilesToImport = () => ({
113
+ storageHandle: notEmpty(data.storageEntry?.handle),
114
+ files: selectedFiles.value.map((f) => f.handle!),
115
+ });
116
+
117
+ const setDirPath = (dirPath: string) => {
118
+ data.dirPath = dirPath;
119
+ };
120
+
121
+ const selectFile = (ev: MouseEvent, file: FileDialogItem) => {
122
+ const { shiftKey, metaKey } = ev;
123
+ const { lastSelected } = data;
124
+
125
+ ev.preventDefault();
126
+
127
+ if (file.canBeSelected) {
128
+ if (!props.multi) {
129
+ data.items.forEach((f) => (f.selected = false));
130
+ }
131
+
132
+ file.selected = true;
133
+
134
+ if (!props.multi) {
135
+ return;
136
+ }
137
+
138
+ if (!metaKey && !shiftKey) {
139
+ data.items.forEach((f) => {
140
+ if (f.id !== file.id) {
141
+ f.selected = false;
142
+ }
143
+ });
144
+ }
145
+
146
+ if (shiftKey && lastSelected !== undefined) {
147
+ data.items.forEach((f) => {
148
+ if (between(f.id, lastSelected, file.id)) {
149
+ f.selected = true;
150
+ }
151
+ });
152
+ }
153
+
154
+ if (file.selected) {
155
+ data.lastSelected = file.id;
156
+ }
157
+ }
158
+ };
159
+
160
+ const changeAll = (selected: boolean) => {
161
+ if (selected && !props.multi) {
162
+ return;
163
+ }
164
+
165
+ data.items
166
+ .filter((f) => f.canBeSelected)
167
+ .forEach((file) => {
168
+ file.selected = selected;
169
+ });
170
+ };
171
+
172
+ const selectAll = () => changeAll(true);
173
+
174
+ const deselectAll = () => changeAll(false);
175
+
176
+ const loadAvailableStorages = () => {
177
+ resetData();
178
+ deselectAll();
179
+ if (!window.platforma) {
180
+ console.warn('platforma API is not found');
181
+ return;
182
+ }
183
+ window.platforma.lsDriver
184
+ .getStorageList()
185
+ .then((storageEntries) => {
186
+ data.storageOptions = storageEntries.map((it) => ({
187
+ text: it.name,
188
+ value: it,
189
+ }));
190
+
191
+ if (props.autoSelectStorage) {
192
+ tapIf(
193
+ storageEntries.find(
194
+ (e) =>
195
+ e.name === 'local' || // the only local storage on unix systems
196
+ (e.name.startsWith('local_disk_') && e.initialFullPath.length > 4),
197
+ ), // local drive where home folder is stored, normally C:\
198
+ (entry) => {
199
+ data.storageEntry = entry;
200
+ },
201
+ );
202
+ }
203
+ })
204
+ .catch((err) => (data.error = String(err)));
205
+ };
206
+
207
+ watch(
208
+ toRef(data, 'storageEntry'),
209
+ (entry) => {
210
+ resetData();
211
+ data.dirPath = entry?.initialFullPath ?? '';
212
+ },
213
+ { immediate: true },
214
+ );
215
+
216
+ watch([() => data.dirPath, () => data.storageEntry], () => {
217
+ load();
218
+ });
219
+
220
+ watch(
221
+ () => props.modelValue,
222
+ (isOpen) => {
223
+ if (isOpen) {
224
+ loadAvailableStorages();
225
+ } else {
226
+ Object.assign(data, defaultData());
227
+ }
228
+ },
229
+ { immediate: true },
230
+ );
231
+
232
+ useEventListener(document, 'keydown', (ev: KeyboardEvent) => {
233
+ if (!props.modelValue) {
234
+ return;
235
+ }
236
+
237
+ if (ev.target !== document.body) {
238
+ return;
239
+ }
240
+
241
+ if (ev.metaKey && ev.code === 'KeyA') {
242
+ ev.preventDefault();
243
+ selectAll();
244
+ }
245
+
246
+ if (ev.metaKey && ev.shiftKey && ev.code === 'Period') {
247
+ ev.preventDefault();
248
+ data.showHiddenItems = !data.showHiddenItems;
249
+ }
250
+
251
+ if (ev.code === 'Enter') {
252
+ props.submit();
253
+ }
254
+ });
255
+
256
+ defineExpose({
257
+ isReady,
258
+ getFilesToImport,
259
+ });
260
+
261
+ onMounted(loadAvailableStorages);
262
+ </script>
263
+
264
+ <template>
265
+ <div :class="style.remote" @click.stop="deselectAll">
266
+ <div :class="style.search">
267
+ <div>
268
+ <PlDropdown v-model="data.storageEntry" label="Select storage" :options="data.storageOptions" />
269
+ </div>
270
+ <div>
271
+ <PlSearchField v-model="data.search" label="Search in folder" clearable />
272
+ </div>
273
+ </div>
274
+ <div :class="style['ls-container']">
275
+ <div :class="style['ls-head']">
276
+ <div :class="style['breadcrumbs']">
277
+ <template v-for="(s, i) in breadcrumbs" :key="i">
278
+ <div :title="s.path" @click="setDirPath(s.path)">{{ s.name }}</div>
279
+ <PlIcon16 v-if="s.index !== breadcrumbs.length - 1" name="chevron-right" />
280
+ </template>
281
+ </div>
282
+ <div :class="style.selected">Selected: {{ selectedFiles.length }}</div>
283
+ </div>
284
+ <div v-if="data.currentLoadingPath !== undefined" class="ls-loader">
285
+ <i class="mask-24 mask-loading loader-icon" />
286
+ </div>
287
+ <div v-else-if="!data.storageEntry" :class="style['ls-empty']">
288
+ <div :class="style.cat" />
289
+ <div :class="style.message">Select storage to preview</div>
290
+ </div>
291
+ <div v-else-if="data.error" :class="style['ls-error']">
292
+ <div :class="style.cat" />
293
+ <div :class="style.message">{{ data.error }}</div>
294
+ </div>
295
+ <div v-else :class="style['ls-body']">
296
+ <template v-for="file in visibleItems" :key="file.id">
297
+ <div v-if="file.isDir" :class="style.isDir" @click="setDirPath(file.path)">
298
+ <i class="icon-16 icon-chevron-right" />
299
+ <span v-text-overflown :title="file.name">{{ file.name }}</span>
300
+ </div>
301
+ <div
302
+ v-else
303
+ :class="{ [style.canBeSelected]: file.canBeSelected, [style.selected]: file.selected }"
304
+ @click.stop="(ev) => selectFile(ev, file)"
305
+ >
306
+ <i class="mask-16 mask-box" :class="style.isFile" />
307
+ <span v-text-overflown :title="file.name">{{ file.name }}</span>
308
+ </div>
309
+ </template>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ </template>
@@ -0,0 +1,247 @@
1
+ @import "@/assets/mixins";
2
+
3
+ .component {
4
+ .local {
5
+ --pl-file-dialog-local-bg: var(--bg-base-light);
6
+ --pl-file-dialog-local-border-color: var(--border-color-div-grey);
7
+
8
+ display: flex;
9
+ flex-direction: column;
10
+ justify-content: center;
11
+ align-items: center;
12
+ gap: 12px;
13
+ overflow: auto;
14
+ background-color: var(--pl-file-dialog-local-bg);
15
+ border-radius: 6px;
16
+ border: 1px dashed var(--pl-file-dialog-local-border-color);
17
+
18
+ flex: 1;
19
+ margin: 0 24px 24px 24px;
20
+
21
+ &:hover {
22
+ --pl-file-dialog-local-bg: rgba(99, 224, 36, 0.12);
23
+ --pl-file-dialog-local-border-color: var(--border-color-focus);
24
+ }
25
+
26
+ >span {
27
+ font-size: 14px;
28
+ font-weight: 500;
29
+ line-height: 20px;
30
+ /* 142.857% */
31
+ }
32
+
33
+ .supported {
34
+ color: var(--txt-03);
35
+ font-size: 14px;
36
+ font-weight: 500;
37
+ line-height: 20px;
38
+ }
39
+ }
40
+
41
+ .remote {
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: 24px;
45
+ min-height: 0;
46
+ max-width: 100%;
47
+ flex: 1;
48
+
49
+ .search {
50
+ padding-top: 6px;
51
+ display: flex;
52
+ flex-direction: row;
53
+ gap: 12px;
54
+ margin: 0 24px;
55
+
56
+ >div {
57
+ flex: 1;
58
+ }
59
+ }
60
+
61
+ .ls-container {
62
+ display: flex;
63
+ flex-direction: column;
64
+ border-top: 1px solid var(--border-color-div-grey);
65
+ flex: 1;
66
+ min-height: 0;
67
+ }
68
+
69
+ .ls-head {
70
+ display: flex;
71
+ align-items: center;
72
+ max-width: 100%;
73
+ gap: 6px;
74
+ height: 40px;
75
+ font-size: 11px;
76
+ font-style: normal;
77
+ font-weight: 600;
78
+ line-height: 12px;
79
+ letter-spacing: 0.44px;
80
+ border-bottom: 1px solid var(--border-color-div-grey);
81
+ background-color: var(--bg-base-light);
82
+ padding: 0 24px;
83
+
84
+ .selected {
85
+ font-size: 11px;
86
+ font-weight: 600;
87
+ letter-spacing: 0.44px;
88
+ text-transform: uppercase;
89
+ white-space: nowrap;
90
+ margin-left: auto;
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 12px;
94
+ }
95
+
96
+ >div {
97
+ cursor: pointer;
98
+ }
99
+
100
+ .breadcrumbs {
101
+ color: var(--txt-01);
102
+ font-size: 14px;
103
+ line-height: 18px;
104
+ font-weight: 600;
105
+
106
+ display: flex;
107
+ flex-direction: row;
108
+ align-items: center;
109
+ gap: 2px;
110
+ min-width: 0;
111
+ max-width: 100%;
112
+
113
+ >div {
114
+ display: inline-block;
115
+ white-space: nowrap;
116
+ overflow: hidden;
117
+ text-overflow: ellipsis;
118
+ }
119
+
120
+ i {
121
+ display: inline-block;
122
+ min-width: 16px;
123
+ }
124
+ }
125
+ }
126
+
127
+ .ls-error {
128
+ display: flex;
129
+ flex-direction: column;
130
+ gap: 24px;
131
+ align-items: center;
132
+ background-color: var(--bg-base-light);
133
+ height: 366px;
134
+
135
+ .cat {
136
+ margin-top: 40px;
137
+ width: 240px;
138
+ height: 200px;
139
+ background: url('@/assets/images/no-data-cat.svg');
140
+ }
141
+
142
+ .message {
143
+ color: var(--txt-mask);
144
+ text-align: center;
145
+ font-size: 28px;
146
+ line-height: 28px;
147
+ font-weight: 500;
148
+ letter-spacing: -0.56px;
149
+ overflow: hidden;
150
+ }
151
+ }
152
+
153
+ .ls-empty {
154
+ display: flex;
155
+ flex-direction: column;
156
+ gap: 24px;
157
+ align-items: center;
158
+ background-color: var(--bg-base-light);
159
+ height: 366px;
160
+
161
+ .cat {
162
+ margin-top: 40px;
163
+ width: 400px;
164
+ height: 200px;
165
+ background: url('@/assets/images/empty-cat.svg');
166
+ }
167
+
168
+ .message {
169
+ color: var(--txt-mask);
170
+ text-align: center;
171
+ font-size: 28px;
172
+ font-weight: 500;
173
+ letter-spacing: -0.56px;
174
+ }
175
+ }
176
+
177
+ .ls-body {
178
+ padding: 12px;
179
+ min-height: 0;
180
+ flex: 1;
181
+ overflow: auto;
182
+ user-select: none;
183
+ display: flex;
184
+ flex-direction: column;
185
+ @include scrollbar(true);
186
+
187
+ >div {
188
+ font-family: var(--font-family-monospace);
189
+ color: var(--txt-01);
190
+ font-feature-settings: 'ss11' on, 'ss15' on, 'ss17' on;
191
+ font-size: 14px;
192
+ font-weight: 400;
193
+ display: flex;
194
+ align-items: center;
195
+ min-height: 24px;
196
+ gap: 12px;
197
+ padding: 0 12px;
198
+
199
+ >span {
200
+ display: inline-block;
201
+ white-space: nowrap;
202
+ overflow: hidden;
203
+ }
204
+
205
+ >i {
206
+ min-width: 16px;
207
+ }
208
+
209
+ i.isFile {
210
+ display: inline-block;
211
+ background-color: #CFD1DB;
212
+ }
213
+
214
+ &.isDir {
215
+ cursor: pointer;
216
+
217
+ &:hover {
218
+ text-decoration: underline;
219
+ }
220
+ }
221
+
222
+ &.canBeSelected {
223
+ color: var(--txt-01);
224
+
225
+ &.selected {
226
+ background-color: var(--btn-active-select);
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ .ls-loader {
233
+ display: flex;
234
+ align-items: center;
235
+ justify-items: center;
236
+ height: 366px;
237
+ transform: scale(4);
238
+ overflow: hidden;
239
+
240
+ .loader-icon {
241
+ animation: spin 4s linear infinite;
242
+ background-color: #ccc;
243
+ margin: auto;
244
+ }
245
+ }
246
+ }
247
+ }
@@ -0,0 +1,45 @@
1
+ import type { Option } from '@milaboratories/helpers';
2
+ import type { StorageEntry } from '@platforma-sdk/model';
3
+ import type { FileDialogItem } from './utils';
4
+ import { computed } from 'vue';
5
+
6
+ export const defaultData = () => ({
7
+ dirPath: '' as string,
8
+ search: '',
9
+ storageEntry: undefined as StorageEntry | undefined,
10
+ items: [] as FileDialogItem[],
11
+ error: '',
12
+ storageOptions: [] as Option<StorageEntry>[],
13
+ selected: [],
14
+ lastSelected: undefined as number | undefined,
15
+ currentLoadingPath: undefined as string | undefined,
16
+ showHiddenItems: false,
17
+ });
18
+
19
+ export type Data = ReturnType<typeof defaultData>;
20
+
21
+ export function useVisibleItems(data: Data) {
22
+ return computed(() => {
23
+ let items = data.items;
24
+
25
+ if (!data.showHiddenItems) {
26
+ items = items.filter((it) => !it.name.startsWith('.'));
27
+ }
28
+
29
+ if (data.search) {
30
+ const search = data.search.toLocaleLowerCase();
31
+ items = items.filter((it) => it.name.toLocaleLowerCase().includes(search));
32
+ }
33
+
34
+ return items;
35
+ });
36
+ }
37
+
38
+ export const vTextOverflown = {
39
+ mounted: (el: HTMLElement) => {
40
+ if (el.clientWidth < el.scrollWidth) {
41
+ const s = el.innerText;
42
+ el.innerText = s.substring(0, 57) + '...' + s.substring(s.length - 10);
43
+ }
44
+ },
45
+ };
@@ -1,5 +1,10 @@
1
+ import { trimCharsLeft } from '@milaboratories/helpers';
1
2
  import type { ImportFileHandle } from '@platforma-sdk/model';
2
3
 
4
+ export function normalizeExtensions(extensions: string[] | undefined) {
5
+ return extensions ? extensions.map((it) => '.' + trimCharsLeft(it, ['.'])) : undefined;
6
+ }
7
+
3
8
  export function getFilePathBreadcrumbs(filePath: string) {
4
9
  const chunks = filePath.split('/');
5
10
 
@@ -24,6 +24,7 @@
24
24
 
25
25
  padding: 0 8px;
26
26
  gap: 8px;
27
+ cursor: pointer;
27
28
 
28
29
  &__envelope {
29
30
  font-family: var(--font-family-base);
@@ -64,11 +65,7 @@
64
65
  white-space: nowrap;
65
66
  overflow: hidden;
66
67
  text-overflow: ellipsis;
67
- cursor: pointer;
68
-
69
- &:focus {
70
- outline: none;
71
- }
68
+ line-height: 18px;
72
69
 
73
70
  &:empty::before {
74
71
  color: var(--color-placeholder);