@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.
- package/CHANGELOG.md +13 -0
- package/dist/pl-uikit.js +3218 -2989
- package/dist/pl-uikit.umd.cjs +6 -6
- package/dist/src/components/ContextProvider.vue.d.ts +7 -8
- package/dist/src/components/DataTable/AddColumnBtn.vue.d.ts +1 -1
- package/dist/src/components/DataTable/BaseCellComponent.vue.d.ts +5 -8
- package/dist/src/components/DataTable/ColumnCaret.vue.d.ts +3 -4
- package/dist/src/components/DataTable/TScroll.vue.d.ts +5 -8
- package/dist/src/components/DataTable/TableComponent.vue.d.ts +5 -6
- package/dist/src/components/DataTable/TdCell.vue.d.ts +3 -4
- package/dist/src/components/DataTable/ThCell.vue.d.ts +5 -6
- package/dist/src/components/DataTable/TrBody.vue.d.ts +6 -6
- package/dist/src/components/DataTable/TrHead.vue.d.ts +3 -2
- package/dist/src/components/DataTable/assets/TableIcon.vue.d.ts +1 -1
- package/dist/src/components/DropdownListItem.vue.d.ts +5 -10
- package/dist/src/components/GridTable/AddColumnBtn.vue.d.ts +1 -1
- package/dist/src/components/GridTable/TRow.vue.d.ts +8 -10
- package/dist/src/components/GridTable/TdCell.vue.d.ts +10 -12
- package/dist/src/components/GridTable/ThCell.vue.d.ts +5 -8
- package/dist/src/components/GridTable/assets/TableIcon.vue.d.ts +1 -1
- package/dist/src/components/GridTable/index.vue.d.ts +8 -8
- package/dist/src/components/HScroll.vue.d.ts +4 -7
- package/dist/src/components/InputRange.vue.d.ts +4 -6
- package/dist/src/components/LongText.vue.d.ts +3 -2
- package/dist/src/components/PlAccordion/ExpandTransition.vue.d.ts +2 -1
- package/dist/src/components/PlAccordion/PlAccordion.vue.d.ts +6 -5
- package/dist/src/components/PlAccordion/PlAccordionSection.vue.d.ts +6 -5
- package/dist/src/components/PlBtnGhost/PlBtnGhost.vue.d.ts +1 -1
- package/dist/src/components/PlBtnGroup/PlBtnGroup.vue.d.ts +1 -1
- package/dist/src/components/PlBtnLink/PlBtnLink.vue.d.ts +12 -35
- package/dist/src/components/PlBtnSplit/PlBtnSplit.vue.d.ts +3 -4
- package/dist/src/components/PlCheckbox/PlCheckboxBase.vue.d.ts +5 -7
- package/dist/src/components/PlCheckboxGroup/PlCheckboxGroup.vue.d.ts +1 -1
- package/dist/src/components/PlChip/PlChip.vue.d.ts +8 -9
- package/dist/src/components/PlDropdown/PlDropdown.vue.d.ts +1 -1
- package/dist/src/components/PlDropdownLegacy/PlDropdownLegacy.vue.d.ts +1 -1
- package/dist/src/components/PlDropdownLine/PlDropdownLine.vue.d.ts +16 -23
- package/dist/src/components/PlDropdownLine/ResizableInput.vue.d.ts +5 -11
- package/dist/src/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts +1 -1
- package/dist/src/components/PlDropdownRef/PlDropdownRef.vue.d.ts +1 -1
- package/dist/src/components/PlEditableTitle/PlEditableTitle.vue.d.ts +5 -5
- package/dist/src/components/PlFileDialog/Local.vue.d.ts +8 -0
- package/dist/src/components/PlFileDialog/PlFileDialog.vue.d.ts +84 -12
- package/dist/src/components/PlFileDialog/Remote.vue.d.ts +28 -0
- package/dist/src/components/PlFileDialog/remote.d.ts +20 -0
- package/dist/src/components/PlFileDialog/utils.d.ts +1 -0
- package/dist/src/components/PlFileInput/PlFileInput.vue.d.ts +18 -65
- package/dist/src/components/PlIcon16/PlIcon16.vue.d.ts +3 -5
- package/dist/src/components/PlIcon24/PlIcon24.vue.d.ts +3 -5
- package/dist/src/components/PlLogView/PlLogView.vue.d.ts +1 -1
- package/dist/src/components/PlMaskIcon16/PlMaskIcon16.vue.d.ts +3 -5
- package/dist/src/components/PlMaskIcon24/PlMaskIcon24.vue.d.ts +3 -5
- package/dist/src/components/PlNotificationAlert/PlNotificationAlert.vue.d.ts +6 -5
- package/dist/src/components/PlNumberField/PlNumberField.vue.d.ts +3 -2
- package/dist/src/components/PlProgressBar/PlProgressBar.vue.d.ts +3 -6
- package/dist/src/components/PlSearchField/PlSearchField.vue.d.ts +12 -0
- package/dist/src/components/PlSearchField/index.d.ts +1 -0
- package/dist/src/components/PlSectionSeparator/PlSectionSeparator.vue.d.ts +9 -12
- package/dist/src/components/PlSpacer/PlSpacer.vue.d.ts +1 -1
- package/dist/src/components/PlTabs/PlTabs.vue.d.ts +1 -1
- package/dist/src/components/PlTabs/Tab.vue.d.ts +6 -6
- package/dist/src/components/PlTextArea/PlTextArea.vue.d.ts +1 -1
- package/dist/src/components/PlTextField/PlTextField.vue.d.ts +3 -5
- package/dist/src/components/PlToggleSwitch/PlToggleSwitch.vue.d.ts +1 -1
- package/dist/src/components/PlTooltip/Beak.vue.d.ts +1 -1
- package/dist/src/components/Scrollable.vue.d.ts +3 -2
- package/dist/src/components/Slider.vue.d.ts +18 -28
- package/dist/src/components/SliderRange.vue.d.ts +18 -28
- package/dist/src/components/SliderRangeTriple.vue.d.ts +19 -29
- package/dist/src/components/TabItem.vue.d.ts +4 -7
- package/dist/src/components/ThemeSwitcher.vue.d.ts +1 -1
- package/dist/src/components/TransitionSlidePanel.vue.d.ts +2 -1
- package/dist/src/components/VScroll.vue.d.ts +4 -7
- package/dist/src/components/contextMenu/Menu.vue.d.ts +5 -6
- package/dist/src/drafts/FileBaseInput.vue.d.ts +3 -2
- package/dist/src/index.d.ts +1 -0
- package/dist/src/layout/PlBlockPage/PlBlockPage.vue.d.ts +1 -1
- package/dist/src/layout/PlContainer/PlContainer.vue.d.ts +1 -1
- package/dist/src/layout/PlGrid/PlGrid.vue.d.ts +1 -1
- package/dist/src/layout/PlRow/PlRow.vue.d.ts +1 -1
- package/dist/src/layout/PlSpacer/PlSpacer.vue.d.ts +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/src/utils/CloseModalBtn.vue.d.ts +1 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/components/PlBtnSplit/PlBtnSplit.vue +1 -2
- package/src/components/PlDialogModal/pl-dialog-modal.scss +0 -1
- package/src/components/PlFileDialog/Local.vue +95 -0
- package/src/components/PlFileDialog/PlFileDialog.vue +63 -310
- package/src/components/PlFileDialog/Remote.vue +313 -0
- package/src/components/PlFileDialog/pl-file-dialog.module.scss +247 -0
- package/src/components/PlFileDialog/remote.ts +45 -0
- package/src/components/PlFileDialog/utils.ts +5 -0
- package/src/components/PlFileInput/pl-file-input.scss +2 -5
- package/src/components/PlSearchField/PlSearchField.vue +85 -0
- package/src/components/PlSearchField/index.ts +1 -0
- package/src/components/PlTabs/Tab.vue +0 -1
- package/src/composition/useSortable.ts +4 -0
- package/src/index.ts +1 -0
- package/src/types.ts +1 -1
- 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.
|
|
3
|
+
"version": "2.2.18",
|
|
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": "^
|
|
26
|
-
"jsdom": "^
|
|
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
|
|
28
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
29
29
|
"tsc-alias": "^1.8.10",
|
|
30
|
-
"vitest": "^2.1.
|
|
30
|
+
"vitest": "^2.1.6",
|
|
31
31
|
"vite": "^5.4.11",
|
|
32
|
-
"vue-tsc": "^2.1.
|
|
32
|
+
"vue-tsc": "^2.1.10",
|
|
33
33
|
"yarpm": "^1.2.0",
|
|
34
34
|
"svgo": "^3.3.2",
|
|
35
|
-
"@milaboratories/helpers": "^1.6.
|
|
36
|
-
"@platforma-sdk/model": "^1.13.
|
|
35
|
+
"@milaboratories/helpers": "^1.6.8",
|
|
36
|
+
"@platforma-sdk/model": "^1.13.15"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"dev": "vite",
|
|
@@ -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) => {
|
|
@@ -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 {
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
64
|
+
const defaultTitle = computed(() => (props.multi ? 'Select Files to Import' : 'Select File to Import'));
|
|
52
65
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
77
|
+
const closeModal = () => emit('update:modelValue', false);
|
|
136
78
|
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
const closeModal = () => {
|
|
140
|
-
emit('update:modelValue', false);
|
|
141
|
-
};
|
|
79
|
+
const remoteRef = useTemplateRef('remote');
|
|
142
80
|
|
|
143
81
|
const submit = () => {
|
|
144
|
-
if (
|
|
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
|
|
154
|
-
|
|
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="
|
|
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 ??
|
|
316
|
-
<div
|
|
317
|
-
<
|
|
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
|
-
<
|
|
359
|
-
|
|
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>
|