@milaboratories/uikit 2.2.17 → 2.2.19

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 (103) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/pl-uikit.js +3217 -2988
  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/assets/mixins.scss +19 -6
  88. package/src/components/PlBtnSplit/PlBtnSplit.vue +1 -2
  89. package/src/components/PlDialogModal/pl-dialog-modal.scss +0 -1
  90. package/src/components/PlDropdown/pl-dropdown.scss +5 -1
  91. package/src/components/PlFileDialog/Local.vue +95 -0
  92. package/src/components/PlFileDialog/PlFileDialog.vue +63 -310
  93. package/src/components/PlFileDialog/Remote.vue +313 -0
  94. package/src/components/PlFileDialog/pl-file-dialog.module.scss +247 -0
  95. package/src/components/PlFileDialog/remote.ts +45 -0
  96. package/src/components/PlFileDialog/utils.ts +5 -0
  97. package/src/components/PlFileInput/pl-file-input.scss +7 -5
  98. package/src/components/PlSearchField/PlSearchField.vue +85 -0
  99. package/src/components/PlSearchField/index.ts +1 -0
  100. package/src/components/PlTabs/Tab.vue +0 -1
  101. package/src/index.ts +1 -0
  102. package/src/types.ts +1 -1
  103. package/src/components/PlFileDialog/pl-file-dialog.scss +0 -172
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/uikit",
3
- "version": "2.2.17",
3
+ "version": "2.2.19",
4
4
  "type": "module",
5
5
  "main": "dist/pl-uikit.umd.js",
6
6
  "module": "dist/pl-uikit.js",
@@ -22,18 +22,18 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@vue/test-utils": "^2.4.6",
25
- "@vueuse/core": "^11.2.0",
26
- "jsdom": "^24.1.3",
25
+ "@vueuse/core": "^12.0.0",
26
+ "jsdom": "^25.0.1",
27
27
  "resize-observer-polyfill": "^1.5.1",
28
- "@vitejs/plugin-vue": "^5.1.4",
28
+ "@vitejs/plugin-vue": "^5.2.1",
29
29
  "tsc-alias": "^1.8.10",
30
- "vitest": "^2.1.5",
30
+ "vitest": "^2.1.6",
31
31
  "vite": "^5.4.11",
32
- "vue-tsc": "^2.1.6",
32
+ "vue-tsc": "^2.1.10",
33
33
  "yarpm": "^1.2.0",
34
34
  "svgo": "^3.3.2",
35
- "@milaboratories/helpers": "^1.6.7",
36
- "@platforma-sdk/model": "^1.13.5"
35
+ "@milaboratories/helpers": "^1.6.8",
36
+ "@platforma-sdk/model": "^1.14.1"
37
37
  },
38
38
  "scripts": {
39
39
  "dev": "vite",
@@ -23,7 +23,9 @@
23
23
 
24
24
  @if $h ==null {
25
25
  height: $w;
26
- } @else {
26
+ }
27
+
28
+ @else {
27
29
  height: $h;
28
30
  }
29
31
  }
@@ -34,7 +36,9 @@
34
36
 
35
37
  @if $h ==null {
36
38
  height: $w;
37
- } @else {
39
+ }
40
+
41
+ @else {
38
42
  height: $h;
39
43
  }
40
44
  }
@@ -46,7 +50,9 @@
46
50
 
47
51
  @if $h ==null {
48
52
  height: $w;
49
- } @else {
53
+ }
54
+
55
+ @else {
50
56
  height: $h;
51
57
  }
52
58
  }
@@ -64,7 +70,9 @@
64
70
 
65
71
  @if $h ==null {
66
72
  height: $w;
67
- } @else {
73
+ }
74
+
75
+ @else {
68
76
  height: $h;
69
77
  }
70
78
  }
@@ -98,6 +106,7 @@
98
106
  &::-webkit-scrollbar-thumb {
99
107
  background: var(--thumb-color);
100
108
  border-radius: 5px;
109
+
101
110
  &:hover {
102
111
  --thumb-color: var(--border-color-focus);
103
112
  }
@@ -143,7 +152,11 @@
143
152
  font-size: 12px;
144
153
  font-weight: 500;
145
154
 
146
- > span {
155
+ border-bottom-right-radius: 4px;
156
+ border-bottom-left-radius: 4px;
157
+ background: var(--bg-elevated-01);
158
+
159
+ >span {
147
160
  overflow: hidden;
148
161
  white-space: pre;
149
162
  text-overflow: ellipsis;
@@ -202,4 +215,4 @@
202
215
  border: 2px solid transparent;
203
216
  border-right: 1px solid transparent;
204
217
  }
205
- }
218
+ }
@@ -24,6 +24,7 @@ const props = defineProps<{
24
24
  */
25
25
  loading?: boolean;
26
26
  }>();
27
+
27
28
  const emits = defineEmits(['click']);
28
29
 
29
30
  const model = defineModel<M>({ required: true });
@@ -93,8 +94,6 @@ useElementPosition(root, (pos) => {
93
94
 
94
95
  optionsStyle.left = pos.left + 'px';
95
96
  optionsStyle.width = pos.width + 'px';
96
-
97
- console.log(pos.top, optionsStyle);
98
97
  });
99
98
 
100
99
  const selectOption = (v: M | undefined) => {
@@ -50,7 +50,6 @@
50
50
  padding: 16px 24px 40px;
51
51
  min-height: 0;
52
52
  line-height: 20px;
53
- // background-color: #33333333; //
54
53
  @include scrollbar();
55
54
 
56
55
  &.no-content-gutters {
@@ -132,7 +132,7 @@
132
132
  padding: 0 60px 0 11px; // @TODO padding-right based on controls width
133
133
  pointer-events: none;
134
134
  line-height: var(--control-height);
135
- color: var(--contour-color);
135
+ color: var(--txt-01);
136
136
  overflow: hidden;
137
137
  white-space: pre;
138
138
  text-overflow: ellipsis;
@@ -254,5 +254,9 @@
254
254
  cursor: not-allowed;
255
255
  pointer-events: none;
256
256
  user-select: none;
257
+
258
+ .input-value {
259
+ color: var(--dis-01);
260
+ }
257
261
  }
258
262
  }
@@ -0,0 +1,95 @@
1
+ <script setup lang="ts">
2
+ import style from './pl-file-dialog.module.scss';
3
+ import type { ImportedFiles } from '@/types';
4
+ import { PlIcon24 } from '../PlIcon24';
5
+ import { computed, reactive } from 'vue';
6
+ import type { OpenDialogFilter } from '@platforma-sdk/model';
7
+ import { normalizeExtensions } from './utils';
8
+
9
+ const props = defineProps<{
10
+ importFiles: (value: ImportedFiles) => void;
11
+ multi?: boolean;
12
+ extensions?: string[];
13
+ }>();
14
+
15
+ const data = reactive({
16
+ error: undefined as unknown,
17
+ });
18
+
19
+ const label = computed(() => (props.multi ? 'Drag & Drop files here or click to add' : 'Drag & Drop file here or click to add'));
20
+
21
+ const onDrop = async (ev: DragEvent) => {
22
+ const fileToImportHandle = window.platforma?.lsDriver?.fileToImportHandle;
23
+
24
+ if (!fileToImportHandle) {
25
+ return console.error('API platforma.lsDriver.fileToImportHandle is not available');
26
+ }
27
+
28
+ const extensions = normalizeExtensions(props.extensions);
29
+
30
+ const files = await Promise.all(
31
+ [...(ev.dataTransfer?.files ?? [])]
32
+ .filter((f) => !!f)
33
+ .filter((f) => (extensions ? extensions.some((ext) => f.name.endsWith(ext)) : true))
34
+ .map((file) => {
35
+ return fileToImportHandle(file);
36
+ }),
37
+ );
38
+
39
+ if (files.length) {
40
+ props.importFiles({
41
+ files,
42
+ });
43
+ }
44
+ };
45
+
46
+ const openNativeDialog = async () => {
47
+ const filters: OpenDialogFilter[] = props.extensions
48
+ ? [
49
+ {
50
+ name: 'All Files',
51
+ extensions: props.extensions,
52
+ },
53
+ ]
54
+ : [];
55
+
56
+ if (props.multi) {
57
+ window.platforma?.lsDriver
58
+ .showOpenMultipleFilesDialog({
59
+ title: 'Select files to import',
60
+ filters,
61
+ })
62
+ .then(({ files }) => {
63
+ if (files) {
64
+ props.importFiles({
65
+ files,
66
+ });
67
+ }
68
+ })
69
+ .catch((err) => (data.error = err));
70
+ } else {
71
+ window.platforma?.lsDriver
72
+ .showOpenSingleFileDialog({
73
+ title: 'Select file to import',
74
+ filters,
75
+ })
76
+ .then(({ file }) => {
77
+ if (file) {
78
+ props.importFiles({
79
+ files: [file],
80
+ });
81
+ }
82
+ })
83
+ .catch((err) => (data.error = err));
84
+ }
85
+ };
86
+ </script>
87
+
88
+ <template>
89
+ <div :class="style.local" @drop="onDrop" @dragover.prevent @click="openNativeDialog">
90
+ <PlIcon24 name="cloud-upload" />
91
+ <span>{{ label }}</span>
92
+ <span v-if="extensions" :class="style.supported">Supported formats: {{ extensions.join(', ') }}</span>
93
+ <span v-if="data.error" class="alert-error">{{ data.error }}</span>
94
+ </div>
95
+ </template>
@@ -1,17 +1,14 @@
1
1
  <script lang="ts" setup>
2
- import './pl-file-dialog.scss';
3
- import { watch, reactive, computed, toRef, onUpdated } from 'vue';
4
- import { between, notEmpty, tapIf } from '@milaboratories/helpers';
5
- import type { Option } from '@milaboratories/helpers';
6
- import type { StorageEntry, StorageHandle } from '@platforma-sdk/model';
7
- import type { ImportedFiles } from '@/types';
8
- import { getFilePathBreadcrumbs, type FileDialogItem } from './utils';
9
- import { PlTextField } from '../PlTextField';
2
+ import style from './pl-file-dialog.module.scss';
3
+ import { computed, ref, useTemplateRef } from 'vue';
4
+ import { notEmpty } from '@milaboratories/helpers';
5
+ import type { ImportedFiles, SimpleOption } from '@/types';
10
6
  import { PlDialogModal } from '../PlDialogModal';
11
- import { PlDropdown } from '../PlDropdown';
12
7
  import { PlBtnPrimary } from '../PlBtnPrimary';
13
8
  import { PlBtnGhost } from '../PlBtnGhost';
14
- import { useEventListener } from '@/composition/useEventListener';
9
+ import { PlBtnGroup } from '../PlBtnGroup';
10
+ import Remote from './Remote.vue';
11
+ import Local from './Local.vue';
15
12
 
16
13
  const emit = defineEmits<{
17
14
  (e: 'update:modelValue', value: boolean): void;
@@ -20,11 +17,38 @@ const emit = defineEmits<{
20
17
 
21
18
  const props = withDefaults(
22
19
  defineProps<{
20
+ /**
21
+ * Controls the visibility of the modal.
22
+ *
23
+ * When `true`, the modal is open. When `false`, the modal is closed.
24
+ */
23
25
  modelValue: boolean;
24
- extensions?: string[]; // with dot, like ['.fastq.gz', '.fastq']
26
+ /**
27
+ * Specifies the file extensions that are allowed for selection.
28
+ *
29
+ * Provide an array of strings representing file extensions (leading dot can be omitted)
30
+ * If not specified, all file types are allowed.
31
+ */
32
+ extensions?: string[];
33
+ /**
34
+ * Enables the selection of multiple files.
35
+ *
36
+ * When `true`, the user can select multiple files.
37
+ * When `false` or not specified, only a single file can be selected.
38
+ */
25
39
  multi?: boolean;
40
+ /**
41
+ * The custom title of the dialog.
42
+ */
26
43
  title?: string;
44
+ /**
45
+ * Automatically selects the initial storage option.
46
+ * When `true`, the default storage is pre-selected for the user (default: `true`)
47
+ */
27
48
  autoSelectStorage?: boolean;
49
+ /**
50
+ * If `true`, the modal window closes when clicking outside the modal area (default: `true`)
51
+ */
28
52
  closeOnOutsideClick?: boolean;
29
53
  }>(),
30
54
  {
@@ -35,269 +59,35 @@ const props = withDefaults(
35
59
  },
36
60
  );
37
61
 
38
- const defaultData = () => ({
39
- dirPath: '' as string,
40
- search: '',
41
- storageEntry: undefined as StorageEntry | undefined,
42
- items: [] as FileDialogItem[],
43
- error: '',
44
- storageOptions: [] as Option<StorageEntry>[],
45
- selected: [],
46
- lastSelected: undefined as number | undefined,
47
- currentLoadingPath: undefined as string | undefined,
48
- showHiddenItems: false,
49
- });
62
+ const mode = ref<'local' | 'remote'>('local');
50
63
 
51
- const data = reactive(defaultData());
64
+ const defaultTitle = computed(() => (props.multi ? 'Select Files to Import' : 'Select File to Import'));
52
65
 
53
- const resetData = () => {
54
- data.search = '';
55
- data.error = '';
56
- data.lastSelected = undefined;
57
- };
58
-
59
- const visibleItems = computed(() => {
60
- let items = data.items;
61
-
62
- if (!data.showHiddenItems) {
63
- items = items.filter((it) => !it.name.startsWith('.'));
64
- }
65
-
66
- if (data.search) {
67
- const search = data.search.toLocaleLowerCase();
68
- items = items.filter((it) => it.name.toLocaleLowerCase().includes(search));
69
- }
70
-
71
- return items;
72
- });
73
-
74
- const lookup = computed(() => {
75
- return {
76
- modelValue: props.modelValue,
77
- dirPath: data.dirPath,
78
- storageHandle: data.storageEntry?.handle,
79
- };
80
- });
81
-
82
- const query = (handle: StorageHandle, dirPath: string) => {
83
- if (!window.platforma) {
84
- return;
85
- }
86
-
87
- if (data.currentLoadingPath === dirPath) {
88
- return;
89
- }
90
-
91
- data.currentLoadingPath = dirPath;
92
-
93
- window.platforma.lsDriver
94
- .listFiles(handle, dirPath)
95
- .then((res) => {
96
- if (dirPath !== data.dirPath) {
97
- return;
98
- }
99
-
100
- data.items = notEmpty(res)
101
- .entries.map((item) => ({
102
- path: item.fullPath,
103
- name: item.name,
104
- isDir: item.type === 'dir',
105
- canBeSelected: item.type === 'file' && (!props.extensions || props.extensions.some((ext) => item.fullPath.endsWith(ext))),
106
- handle: item.type === 'file' ? item.handle : undefined,
107
- selected: false,
108
- }))
109
- .sort((a, b) => {
110
- if (a.isDir && !b.isDir) return -1;
111
- if (!a.isDir && b.isDir) return 1;
112
- // localeCompare for unicode alphabets
113
- return a.name.localeCompare(b.name);
114
- })
115
- .map((it, id) => ({ id, ...it }));
116
-
117
- data.lastSelected = undefined;
118
- })
119
- .catch((err) => (data.error = String(err)))
120
- .finally(() => {
121
- data.currentLoadingPath = undefined;
122
- });
123
- };
124
-
125
- const load = () => {
126
- resetData();
127
- const { storageHandle, dirPath, modelValue } = lookup.value;
128
- if (storageHandle && modelValue) {
129
- query(storageHandle, dirPath);
130
- }
131
- };
132
-
133
- const breadcrumbs = computed(() => getFilePathBreadcrumbs(data.dirPath));
66
+ const modeOptions = [
67
+ {
68
+ label: 'My Computer',
69
+ value: 'local',
70
+ },
71
+ {
72
+ label: 'Remote',
73
+ value: 'remote',
74
+ },
75
+ ] satisfies SimpleOption[];
134
76
 
135
- const selectedFiles = computed(() => data.items.filter((f) => f.canBeSelected && f.selected && !f.isDir));
77
+ const closeModal = () => emit('update:modelValue', false);
136
78
 
137
- const isReady = computed(() => selectedFiles.value.length > 0);
138
-
139
- const closeModal = () => {
140
- emit('update:modelValue', false);
141
- };
79
+ const remoteRef = useTemplateRef('remote');
142
80
 
143
81
  const submit = () => {
144
- if (isReady.value && data.storageEntry?.handle) {
145
- emit('import:files', {
146
- storageHandle: data.storageEntry.handle,
147
- files: selectedFiles.value.map((f) => f.handle!),
148
- });
82
+ if (remoteRef.value?.isReady) {
83
+ emit('import:files', notEmpty(remoteRef.value?.getFilesToImport()));
149
84
  closeModal();
150
85
  }
151
86
  };
152
87
 
153
- const setDirPath = (dirPath: string) => {
154
- data.dirPath = dirPath;
155
- };
156
-
157
- const selectFile = (ev: MouseEvent, file: FileDialogItem) => {
158
- const { shiftKey, metaKey } = ev;
159
- const { lastSelected } = data;
160
-
161
- ev.preventDefault();
162
-
163
- if (file.canBeSelected) {
164
- if (!props.multi) {
165
- data.items.forEach((f) => (f.selected = false));
166
- }
167
-
168
- file.selected = true;
169
-
170
- if (!props.multi) {
171
- return;
172
- }
173
-
174
- if (!metaKey && !shiftKey) {
175
- data.items.forEach((f) => {
176
- if (f.id !== file.id) {
177
- f.selected = false;
178
- }
179
- });
180
- }
181
-
182
- if (shiftKey && lastSelected !== undefined) {
183
- data.items.forEach((f) => {
184
- if (between(f.id, lastSelected, file.id)) {
185
- f.selected = true;
186
- }
187
- });
188
- }
189
-
190
- if (file.selected) {
191
- data.lastSelected = file.id;
192
- }
193
- }
194
- };
195
-
196
- const changeAll = (selected: boolean) => {
197
- if (selected && !props.multi) {
198
- return;
199
- }
200
-
201
- data.items
202
- .filter((f) => f.canBeSelected)
203
- .forEach((file) => {
204
- file.selected = selected;
205
- });
206
- };
207
-
208
- const selectAll = () => changeAll(true);
209
-
210
- const deselectAll = () => changeAll(false);
211
-
212
- const loadAvailableStorages = () => {
213
- resetData();
214
- deselectAll();
215
- if (!window.platforma) {
216
- console.warn('platforma API is not found');
217
- return;
218
- }
219
- window.platforma.lsDriver
220
- .getStorageList()
221
- .then((storageEntries) => {
222
- data.storageOptions = storageEntries.map((it) => ({
223
- text: it.name,
224
- value: it,
225
- }));
226
-
227
- if (props.autoSelectStorage) {
228
- tapIf(
229
- storageEntries.find(
230
- (e) =>
231
- e.name === 'local' || // the only local storage on unix systems
232
- (e.name.startsWith('local_disk_') && e.initialFullPath.length > 4),
233
- ), // local drive where home folder is stored, normally C:\
234
- (entry) => {
235
- data.storageEntry = entry;
236
- },
237
- );
238
- }
239
- })
240
- .catch((err) => (data.error = String(err)));
241
- };
242
-
243
- watch(
244
- toRef(data, 'storageEntry'),
245
- (entry) => {
246
- resetData();
247
- data.dirPath = entry?.initialFullPath ?? '';
248
- },
249
- { immediate: true },
250
- );
251
-
252
- watch([() => data.dirPath, () => data.storageEntry], () => {
253
- load();
254
- });
255
-
256
- watch(
257
- () => props.modelValue,
258
- (isOpen) => {
259
- if (isOpen) {
260
- loadAvailableStorages();
261
- } else {
262
- Object.assign(data, defaultData());
263
- }
264
- },
265
- { immediate: true },
266
- );
267
-
268
- useEventListener(document, 'keydown', (ev: KeyboardEvent) => {
269
- if (!props.modelValue) {
270
- return;
271
- }
272
-
273
- if (ev.target !== document.body) {
274
- return;
275
- }
276
-
277
- if (ev.metaKey && ev.code === 'KeyA') {
278
- ev.preventDefault();
279
- selectAll();
280
- }
281
-
282
- if (ev.metaKey && ev.shiftKey && ev.code === 'Period') {
283
- ev.preventDefault();
284
- data.showHiddenItems = !data.showHiddenItems;
285
- }
286
-
287
- if (ev.code === 'Enter') {
288
- submit();
289
- }
290
- });
291
-
292
- onUpdated(loadAvailableStorages);
293
-
294
- const vTextOverflown = {
295
- mounted: (el: HTMLElement) => {
296
- if (el.clientWidth < el.scrollWidth) {
297
- const s = el.innerText;
298
- el.innerText = s.substring(0, 57) + '...' + s.substring(s.length - 10);
299
- }
300
- },
88
+ const importFiles = (importedFiles: ImportedFiles) => {
89
+ emit('import:files', importedFiles);
90
+ closeModal();
301
91
  };
302
92
  </script>
303
93
 
@@ -305,58 +95,21 @@ const vTextOverflown = {
305
95
  <PlDialogModal
306
96
  :no-content-gutters="true"
307
97
  :close-on-outside-click="closeOnOutsideClick"
308
- class="split"
98
+ class="pl-dialog-modal"
99
+ :class="style.component"
309
100
  :model-value="modelValue"
310
101
  width="688px"
311
102
  height="720px"
312
103
  @update:model-value="closeModal"
313
- @click.stop="deselectAll"
314
104
  >
315
- <template #title>{{ title ?? 'Select files' }}</template>
316
- <div class="file-dialog">
317
- <div class="file-dialog__search">
318
- <PlDropdown v-model="data.storageEntry" label="Select storage" :options="data.storageOptions" />
319
- <PlTextField v-model="data.search" label="Search in folder" :clearable="() => ''" />
320
- </div>
321
- <div class="ls-container">
322
- <div class="ls-head">
323
- <div class="ls-head__breadcrumbs">
324
- <template v-for="(s, i) in breadcrumbs" :key="i">
325
- <div :title="s.path" @click="setDirPath(s.path)">{{ s.name }}</div>
326
- <i v-if="s.index !== breadcrumbs.length - 1" class="icon-16 icon-chevron-right" />
327
- </template>
328
- </div>
329
- <div class="d-flex ml-auto align-center gap-12">
330
- <span class="ls-head__selected">Selected: {{ selectedFiles.length }}</span>
331
- </div>
332
- </div>
333
- <div v-if="data.currentLoadingPath !== undefined" class="ls-loader">
334
- <i class="mask-24 mask-loading loader-icon" />
335
- </div>
336
- <div v-else-if="!data.storageEntry" class="ls-empty">
337
- <div class="ls-empty__cat" />
338
- <div class="ls-empty__message">Select storage to preview</div>
339
- </div>
340
- <div v-else-if="data.error" class="ls-error">
341
- <div class="ls-error__cat" />
342
- <div class="ls-error__message">{{ data.error }}</div>
343
- </div>
344
- <div v-else class="ls-body">
345
- <template v-for="file in visibleItems" :key="file.id">
346
- <div v-if="file.isDir" class="isDir" @click="setDirPath(file.path)">
347
- <i class="icon-16 icon-chevron-right" />
348
- <span v-text-overflown :title="file.name">{{ file.name }}</span>
349
- </div>
350
- <div v-else :class="{ canBeSelected: file.canBeSelected, selected: file.selected }" @click.stop="(ev) => selectFile(ev, file)">
351
- <i class="mask-16 mask-box isFile" />
352
- <span v-text-overflown :title="file.name">{{ file.name }}</span>
353
- </div>
354
- </template>
355
- </div>
356
- </div>
105
+ <template #title>{{ title ?? defaultTitle }}</template>
106
+ <div style="margin: 0 24px">
107
+ <PlBtnGroup v-model="mode" :options="modeOptions" />
357
108
  </div>
358
- <template #actions>
359
- <PlBtnPrimary style="min-width: 160px" :disabled="!isReady" @click.stop="submit">Import</PlBtnPrimary>
109
+ <Remote v-if="mode === 'remote'" ref="remote" v-bind="$props" :submit="submit" />
110
+ <Local v-if="mode === 'local'" :import-files="importFiles" v-bind="$props" />
111
+ <template v-if="mode === 'remote'" #actions>
112
+ <PlBtnPrimary style="min-width: 160px" :disabled="!remoteRef?.isReady" @click.stop="submit">Import</PlBtnPrimary>
360
113
  <PlBtnGhost :justify-center="false" @click.stop="closeModal">Cancel</PlBtnGhost>
361
114
  </template>
362
115
  </PlDialogModal>