@milaboratories/uikit 2.2.11 → 2.2.13
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 +12 -0
- package/dist/pl-uikit.js +2609 -2576
- package/dist/pl-uikit.umd.cjs +10 -10
- package/dist/src/components/PlFileInput/PlFileInput.vue.d.ts +0 -8
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/components/PlFileDialog/PlFileDialog.vue +26 -27
- package/src/components/PlFileDialog/utils.ts +8 -1
- package/src/components/PlFileInput/PlFileInput.vue +40 -25
- package/src/components/PlFileInput/pl-file-input.scss +2 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/uikit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/pl-uikit.umd.js",
|
|
6
6
|
"module": "dist/pl-uikit.js",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"vue-tsc": "^2.1.6",
|
|
33
33
|
"yarpm": "^1.2.0",
|
|
34
34
|
"svgo": "^3.3.2",
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
35
|
+
"@milaboratories/helpers": "^1.6.7",
|
|
36
|
+
"@platforma-sdk/model": "^1.13.2"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"dev": "vite",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import './pl-file-dialog.scss';
|
|
3
3
|
import { watch, reactive, computed, toRef, onUpdated } from 'vue';
|
|
4
|
-
import { debounce } from '@milaboratories/helpers';
|
|
5
4
|
import { between, notEmpty, tapIf } from '@milaboratories/helpers';
|
|
6
5
|
import type { Option } from '@milaboratories/helpers';
|
|
7
6
|
import type { StorageEntry, StorageHandle } from '@platforma-sdk/model';
|
|
@@ -14,12 +13,6 @@ import { PlBtnPrimary } from '../PlBtnPrimary';
|
|
|
14
13
|
import { PlBtnGhost } from '../PlBtnGhost';
|
|
15
14
|
import { useEventListener } from '@/composition/useEventListener';
|
|
16
15
|
|
|
17
|
-
// const vFocus = {
|
|
18
|
-
// mounted: (el: HTMLElement) => {
|
|
19
|
-
// (el.querySelector('button.pl-btn-primary') as HTMLButtonElement | null)?.focus();
|
|
20
|
-
// },
|
|
21
|
-
// };
|
|
22
|
-
|
|
23
16
|
const emit = defineEmits<{
|
|
24
17
|
(e: 'update:modelValue', value: boolean): void;
|
|
25
18
|
(e: 'import:files', value: ImportedFiles): void;
|
|
@@ -44,6 +37,7 @@ const props = withDefaults(
|
|
|
44
37
|
|
|
45
38
|
const defaultData = () => ({
|
|
46
39
|
dirPath: '' as string,
|
|
40
|
+
search: '',
|
|
47
41
|
storageEntry: undefined as StorageEntry | undefined,
|
|
48
42
|
items: [] as FileDialogItem[],
|
|
49
43
|
error: '',
|
|
@@ -56,12 +50,25 @@ const defaultData = () => ({
|
|
|
56
50
|
|
|
57
51
|
const data = reactive(defaultData());
|
|
58
52
|
|
|
53
|
+
const resetData = () => {
|
|
54
|
+
data.search = '';
|
|
55
|
+
data.error = '';
|
|
56
|
+
data.lastSelected = undefined;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
59
|
const visibleItems = computed(() => {
|
|
60
|
+
let items = data.items;
|
|
61
|
+
|
|
60
62
|
if (!data.showHiddenItems) {
|
|
61
|
-
|
|
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));
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
return
|
|
71
|
+
return items;
|
|
65
72
|
});
|
|
66
73
|
|
|
67
74
|
const lookup = computed(() => {
|
|
@@ -81,10 +88,6 @@ const query = (handle: StorageHandle, dirPath: string) => {
|
|
|
81
88
|
return;
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
data.error = '';
|
|
85
|
-
data.items = [];
|
|
86
|
-
data.lastSelected = undefined;
|
|
87
|
-
|
|
88
91
|
data.currentLoadingPath = dirPath;
|
|
89
92
|
|
|
90
93
|
window.platforma.lsDriver
|
|
@@ -126,10 +129,6 @@ const load = () => {
|
|
|
126
129
|
}
|
|
127
130
|
};
|
|
128
131
|
|
|
129
|
-
const updateDirPathDebounced = debounce((v: string) => {
|
|
130
|
-
data.dirPath = v ?? ''; // ???
|
|
131
|
-
}, 1000);
|
|
132
|
-
|
|
133
132
|
const breadcrumbs = computed(() => getFilePathBreadcrumbs(data.dirPath));
|
|
134
133
|
|
|
135
134
|
const selectedFiles = computed(() => data.items.filter((f) => f.canBeSelected && f.selected && !f.isDir));
|
|
@@ -152,9 +151,6 @@ const submit = () => {
|
|
|
152
151
|
|
|
153
152
|
const setDirPath = (dirPath: string) => {
|
|
154
153
|
data.dirPath = dirPath;
|
|
155
|
-
if (data.storageEntry) {
|
|
156
|
-
load();
|
|
157
|
-
}
|
|
158
154
|
};
|
|
159
155
|
|
|
160
156
|
const selectFile = (ev: MouseEvent, file: FileDialogItem) => {
|
|
@@ -213,8 +209,7 @@ const selectAll = () => changeAll(true);
|
|
|
213
209
|
const deselectAll = () => changeAll(false);
|
|
214
210
|
|
|
215
211
|
const loadAvailableStorages = () => {
|
|
216
|
-
|
|
217
|
-
data.lastSelected = undefined;
|
|
212
|
+
resetData();
|
|
218
213
|
deselectAll();
|
|
219
214
|
if (!window.platforma) {
|
|
220
215
|
console.warn('platforma API is not found');
|
|
@@ -237,7 +232,6 @@ const loadAvailableStorages = () => {
|
|
|
237
232
|
), // local drive where home folder is stored, normally C:\
|
|
238
233
|
(entry) => {
|
|
239
234
|
data.storageEntry = entry;
|
|
240
|
-
data.dirPath = entry.initialFullPath;
|
|
241
235
|
},
|
|
242
236
|
);
|
|
243
237
|
}
|
|
@@ -245,9 +239,14 @@ const loadAvailableStorages = () => {
|
|
|
245
239
|
.catch((err) => (data.error = String(err)));
|
|
246
240
|
};
|
|
247
241
|
|
|
248
|
-
watch(
|
|
249
|
-
data
|
|
250
|
-
|
|
242
|
+
watch(
|
|
243
|
+
toRef(data, 'storageEntry'),
|
|
244
|
+
(entry) => {
|
|
245
|
+
resetData();
|
|
246
|
+
data.dirPath = entry?.initialFullPath ?? '';
|
|
247
|
+
},
|
|
248
|
+
{ immediate: true },
|
|
249
|
+
);
|
|
251
250
|
|
|
252
251
|
watch([() => data.dirPath, () => data.storageEntry], () => {
|
|
253
252
|
load();
|
|
@@ -316,7 +315,7 @@ const vTextOverflown = {
|
|
|
316
315
|
<div class="file-dialog">
|
|
317
316
|
<div class="file-dialog__search">
|
|
318
317
|
<PlDropdown v-model="data.storageEntry" label="Select storage" :options="data.storageOptions" />
|
|
319
|
-
<PlTextField
|
|
318
|
+
<PlTextField v-model="data.search" label="Search in folder" :clearable="() => ''" />
|
|
320
319
|
</div>
|
|
321
320
|
<div class="ls-container">
|
|
322
321
|
<div class="ls-head">
|
|
@@ -3,13 +3,20 @@ import type { ImportFileHandle } from '@platforma-sdk/model';
|
|
|
3
3
|
export function getFilePathBreadcrumbs(filePath: string) {
|
|
4
4
|
const chunks = filePath.split('/');
|
|
5
5
|
|
|
6
|
+
if (chunks[0] !== '') {
|
|
7
|
+
chunks.unshift('');
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
const stack: { index: number; path: string; name: string }[] = [];
|
|
7
11
|
|
|
8
12
|
for (let i = 0; i < chunks.length; i++) {
|
|
9
13
|
stack.push({
|
|
10
14
|
index: i,
|
|
11
15
|
name: i === 0 ? 'Root' : chunks[i],
|
|
12
|
-
path: chunks
|
|
16
|
+
path: chunks
|
|
17
|
+
.slice(0, i + 1)
|
|
18
|
+
.filter((c) => c !== '')
|
|
19
|
+
.join('/'),
|
|
13
20
|
});
|
|
14
21
|
}
|
|
15
22
|
|
|
@@ -4,16 +4,16 @@ import { PlTooltip } from '@/components/PlTooltip';
|
|
|
4
4
|
import { PlFileDialog } from '@/components/PlFileDialog';
|
|
5
5
|
import type { ImportedFiles } from '@/types';
|
|
6
6
|
import { PlMaskIcon24 } from '../PlMaskIcon24';
|
|
7
|
-
import { computed, reactive, ref, useSlots } from 'vue';
|
|
7
|
+
import { computed, reactive, ref, useSlots, watch } from 'vue';
|
|
8
8
|
import type { ImportFileHandle, ImportProgress } from '@platforma-sdk/model';
|
|
9
|
-
import { getFilePathFromHandle } from '@platforma-sdk/model';
|
|
9
|
+
import { getFileNameFromHandle, getFilePathFromHandle } from '@platforma-sdk/model';
|
|
10
10
|
import DoubleContour from '@/utils/DoubleContour.vue';
|
|
11
11
|
import { useLabelNotch } from '@/utils/useLabelNotch';
|
|
12
12
|
import { prettyBytes } from '@milaboratories/helpers';
|
|
13
|
-
import { extractFileName } from './utils';
|
|
14
13
|
|
|
15
14
|
const data = reactive({
|
|
16
15
|
fileDialogOpen: false,
|
|
16
|
+
error: '',
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
const slots = useSlots();
|
|
@@ -60,10 +60,6 @@ const props = withDefaults(
|
|
|
60
60
|
* A helper text to display below the input field when there are no errors.
|
|
61
61
|
*/
|
|
62
62
|
helper?: string;
|
|
63
|
-
/**
|
|
64
|
-
* If `true`, only the file name is displayed, not the full path to it.
|
|
65
|
-
*/
|
|
66
|
-
showFilenameOnly?: boolean;
|
|
67
63
|
/**
|
|
68
64
|
* Remove rounded border and change styles
|
|
69
65
|
*/
|
|
@@ -90,27 +86,30 @@ const props = withDefaults(
|
|
|
90
86
|
},
|
|
91
87
|
);
|
|
92
88
|
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
const filePath = getFilePathFromHandle(props.modelValue as ImportFileHandle).trim();
|
|
97
|
-
return props.showFilenameOnly ? extractFileName(filePath) : filePath;
|
|
98
|
-
} catch (e) {
|
|
99
|
-
console.error(e);
|
|
100
|
-
return props.modelValue;
|
|
101
|
-
}
|
|
89
|
+
const tryValue = <T extends ImportFileHandle>(v: T | undefined, cb: (v: T) => string | undefined) => {
|
|
90
|
+
if (!v) {
|
|
91
|
+
return undefined;
|
|
102
92
|
}
|
|
103
93
|
|
|
104
|
-
|
|
105
|
-
|
|
94
|
+
try {
|
|
95
|
+
return cb(v);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
data.error = err instanceof Error ? err.message : String(err);
|
|
98
|
+
return v;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const fileName = computed(() => tryValue(props.modelValue, getFileNameFromHandle));
|
|
103
|
+
|
|
104
|
+
const filePath = computed(() => tryValue(props.modelValue, getFilePathFromHandle));
|
|
106
105
|
|
|
107
106
|
const isUploading = computed(() => props.progress && !props.progress.done);
|
|
108
107
|
|
|
109
108
|
const isUploaded = computed(() => props.progress && props.progress.done);
|
|
110
109
|
|
|
111
|
-
const
|
|
110
|
+
const computedError = computed(() => data.error ?? props.error);
|
|
112
111
|
|
|
113
|
-
const
|
|
112
|
+
const hasErrors = computed(() => !!computedError.value);
|
|
114
113
|
|
|
115
114
|
const uploadStats = computed(() => {
|
|
116
115
|
const { status, done } = props.progress ?? {};
|
|
@@ -150,6 +149,14 @@ const onImport = (v: ImportedFiles) => {
|
|
|
150
149
|
|
|
151
150
|
const clear = () => emit('update:modelValue', undefined);
|
|
152
151
|
|
|
152
|
+
watch(
|
|
153
|
+
() => props.modelValue,
|
|
154
|
+
() => {
|
|
155
|
+
data.error = '';
|
|
156
|
+
},
|
|
157
|
+
{ immediate: true },
|
|
158
|
+
);
|
|
159
|
+
|
|
153
160
|
const rootRef = ref();
|
|
154
161
|
|
|
155
162
|
if (!props.cellStyle) {
|
|
@@ -159,14 +166,22 @@ if (!props.cellStyle) {
|
|
|
159
166
|
|
|
160
167
|
<template>
|
|
161
168
|
<div :class="{ 'pl-file-input__cell-style': !!cellStyle, 'has-file': !!fileName }" class="pl-file-input__envelope">
|
|
162
|
-
<div
|
|
169
|
+
<div
|
|
170
|
+
ref="rootRef"
|
|
171
|
+
class="pl-file-input"
|
|
172
|
+
tabindex="0"
|
|
173
|
+
:class="{ dashed, error: hasErrors }"
|
|
174
|
+
@keyup.enter="openFileDialog"
|
|
175
|
+
@click.stop="openFileDialog"
|
|
176
|
+
>
|
|
163
177
|
<div class="pl-file-input__progress" :style="progressStyle" />
|
|
164
178
|
<label v-if="!cellStyle && label" ref="label">
|
|
165
179
|
<i v-if="required" class="required-icon" />
|
|
166
180
|
<span>{{ label }}</span>
|
|
167
|
-
<PlTooltip v-if="slots.tooltip" class="info" position="top">
|
|
181
|
+
<PlTooltip v-if="slots.tooltip || filePath" class="info" position="top">
|
|
168
182
|
<template #tooltip>
|
|
169
|
-
<slot name="tooltip" />
|
|
183
|
+
<slot v-if="slots.tooltip" name="tooltip" />
|
|
184
|
+
<template v-else>{{ filePath }}</template>
|
|
170
185
|
</template>
|
|
171
186
|
</PlTooltip>
|
|
172
187
|
</label>
|
|
@@ -174,7 +189,7 @@ if (!props.cellStyle) {
|
|
|
174
189
|
<PlMaskIcon24 v-else-if="isUploading" name="cloud-upload" />
|
|
175
190
|
<PlMaskIcon24 v-else-if="isUploaded" name="success" />
|
|
176
191
|
<PlMaskIcon24 v-else name="paper-clip" />
|
|
177
|
-
<div :data-placeholder="placeholder ?? 'Choose file'" class="pl-file-input__filename"
|
|
192
|
+
<div :data-placeholder="placeholder ?? 'Choose file'" class="pl-file-input__filename">
|
|
178
193
|
{{ fileName }}
|
|
179
194
|
</div>
|
|
180
195
|
<div v-if="uploadStats" class="pl-file-input__stats">{{ uploadStats }}</div>
|
|
@@ -184,7 +199,7 @@ if (!props.cellStyle) {
|
|
|
184
199
|
<div v-if="hasErrors" class="pl-file-input__error">
|
|
185
200
|
{{ computedError }}
|
|
186
201
|
</div>
|
|
187
|
-
<div v-else-if="helper" class="
|
|
202
|
+
<div v-else-if="helper" class="pl-file-input__helper">{{ helper }}</div>
|
|
188
203
|
</div>
|
|
189
204
|
<PlFileDialog
|
|
190
205
|
v-model="data.fileDialogOpen"
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
--icon-color: #000;
|
|
15
15
|
|
|
16
16
|
width: 100%;
|
|
17
|
-
|
|
18
|
-
height: 40px;
|
|
17
|
+
height: var(--control-height, 40px);
|
|
19
18
|
position: relative;
|
|
20
19
|
border-radius: var(--border-radius-control);
|
|
21
20
|
display: flex;
|
|
@@ -28,9 +27,8 @@
|
|
|
28
27
|
|
|
29
28
|
&__envelope {
|
|
30
29
|
font-family: var(--font-family-base);
|
|
31
|
-
height: var(--control-height);
|
|
32
30
|
display: flex;
|
|
33
|
-
|
|
31
|
+
flex-direction: column;
|
|
34
32
|
min-width: 160px;
|
|
35
33
|
}
|
|
36
34
|
|