@cloudron/pankow 3.2.1 → 3.2.2
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/components/Dialog.vue +5 -11
- package/components/InputDialog.vue +3 -14
- package/components/Menu.vue +29 -15
- package/components/MenuItem.vue +1 -1
- package/gallery/Index.vue +330 -330
- package/package.json +1 -2
- package/utils.js +40 -3
package/components/Dialog.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
|
|
3
|
-
import { ref, useTemplateRef } from 'vue';
|
|
3
|
+
import { ref, useTemplateRef, nextTick } from 'vue';
|
|
4
4
|
import Button from './Button.vue';
|
|
5
5
|
import Icon from './Icon.vue';
|
|
6
6
|
|
|
@@ -15,10 +15,6 @@ const props = defineProps({
|
|
|
15
15
|
type: Number,
|
|
16
16
|
default: 2000
|
|
17
17
|
},
|
|
18
|
-
modal: {
|
|
19
|
-
type: Boolean,
|
|
20
|
-
default: false
|
|
21
|
-
},
|
|
22
18
|
showX: {
|
|
23
19
|
type: Boolean,
|
|
24
20
|
default: false
|
|
@@ -52,14 +48,13 @@ const visible = ref(false);
|
|
|
52
48
|
const dialog = useTemplateRef('dialog');
|
|
53
49
|
const dialogBody = useTemplateRef('dialogBody');
|
|
54
50
|
|
|
55
|
-
function open() {
|
|
51
|
+
async function open() {
|
|
56
52
|
visible.value = true;
|
|
57
53
|
|
|
58
54
|
// try to focus some form elements else just the dialog
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}, 100);
|
|
55
|
+
await nextTick();
|
|
56
|
+
const focusElement = dialogBody.value.querySelector('input:not([disabled]):not([style*="display:none"]):not([style*="display: none"])') || dialog.value;
|
|
57
|
+
focusElement.focus();
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
function close() {
|
|
@@ -71,7 +66,6 @@ function onConfirm() {
|
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
function onDismiss() {
|
|
74
|
-
if (props.modal) return;
|
|
75
69
|
onReject();
|
|
76
70
|
}
|
|
77
71
|
|
|
@@ -15,7 +15,6 @@ const confirmLabel = ref('Yes');
|
|
|
15
15
|
const rejectLabel = ref('No');
|
|
16
16
|
const rejectStyle = ref('');
|
|
17
17
|
const isPrompt = ref(false);
|
|
18
|
-
const isModal = ref(true);
|
|
19
18
|
const isMultiValue = ref(false);
|
|
20
19
|
const value = ref([]);
|
|
21
20
|
const required = ref([]);
|
|
@@ -46,7 +45,6 @@ function reset() {
|
|
|
46
45
|
rejectLabel.value = 'No';
|
|
47
46
|
rejectStyle.value = '';
|
|
48
47
|
isPrompt.value = false;
|
|
49
|
-
isModal.value = true;
|
|
50
48
|
isMultiValue.value = false;
|
|
51
49
|
}
|
|
52
50
|
|
|
@@ -62,9 +60,6 @@ function info(options) {
|
|
|
62
60
|
if (options.confirmStyle) confirmStyle.value = options.confirmStyle;
|
|
63
61
|
rejectLabel.value = '';
|
|
64
62
|
|
|
65
|
-
if ('modal' in options) isModal.value = options.modal;
|
|
66
|
-
else isModal.value = true;
|
|
67
|
-
|
|
68
63
|
dialog.value.open();
|
|
69
64
|
|
|
70
65
|
return new Promise((resolve, reject) => {
|
|
@@ -86,9 +81,6 @@ function confirm(options) {
|
|
|
86
81
|
if (options.confirmStyle) confirmStyle.value = options.confirmStyle;
|
|
87
82
|
if (options.rejectStyle) rejectStyle.value = options.rejectStyle;
|
|
88
83
|
|
|
89
|
-
if ('modal' in options) isModal.value = options.modal;
|
|
90
|
-
else isModal.value = true;
|
|
91
|
-
|
|
92
84
|
dialog.value.open();
|
|
93
85
|
|
|
94
86
|
return new Promise((resolve, reject) => {
|
|
@@ -115,9 +107,6 @@ function prompt(options) {
|
|
|
115
107
|
if (options.confirmStyle) confirmStyle.value = options.confirmStyle;
|
|
116
108
|
if (options.rejectStyle) rejectStyle.value = options.rejectStyle;
|
|
117
109
|
|
|
118
|
-
if ('modal' in options) isModal.value = options.modal;
|
|
119
|
-
else isModal.value = true;
|
|
120
|
-
|
|
121
110
|
dialog.value.open();
|
|
122
111
|
|
|
123
112
|
setTimeout(() => { document.getElementById(internalId.value+0).focus(); }, 100);
|
|
@@ -149,14 +138,14 @@ defineExpose({
|
|
|
149
138
|
</script>
|
|
150
139
|
|
|
151
140
|
<template>
|
|
152
|
-
<Dialog ref="dialog" :title="title" :
|
|
153
|
-
<div v-
|
|
154
|
-
<div v-show="isPrompt" class="pankow-input-dialog-prompt" v-for="(msg, index) in message">
|
|
141
|
+
<Dialog ref="dialog" :title="title" :confirm-active="isFormValid" :confirm-label="confirmLabel" :rejectLabel="rejectLabel" :rejectStyle="rejectStyle" :confirmStyle="confirmStyle" @confirm="onConfirmed()" @close="onRejected()" :z-index="2003">
|
|
142
|
+
<div v-if="isPrompt" class="pankow-input-dialog-prompt" v-for="(msg, index) in message">
|
|
155
143
|
<label :for="internalId+index" v-html="msg"></label>
|
|
156
144
|
<PasswordInput :id="internalId+index" v-if="type[index] === 'password'" v-model="value[index]" :placeholder="placeholder[index]" @keydown.enter="onConfirmed()"/>
|
|
157
145
|
<NumberInput :id="internalId+index" v-else-if="type[index] === 'number'" v-model="value[index]" :placeholder="placeholder[index]" @keydown.enter="onConfirmed()"/>
|
|
158
146
|
<TextInput :id="internalId+index" v-else v-model="value[index]" :placeholder="placeholder[index]" @keydown.enter="onConfirmed()"/>
|
|
159
147
|
</div>
|
|
148
|
+
<div v-else v-html="message"></div>
|
|
160
149
|
</Dialog>
|
|
161
150
|
</template>
|
|
162
151
|
|
package/components/Menu.vue
CHANGED
|
@@ -71,8 +71,9 @@ const props = defineProps({
|
|
|
71
71
|
const openEventTimeStamp = ref(0);
|
|
72
72
|
const forElement = ref(null);
|
|
73
73
|
const isOpen = ref(false);
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
let pageX = 0;
|
|
75
|
+
let targetBottom = 0;
|
|
76
|
+
let targetTop = 0;
|
|
76
77
|
const offsetY = ref(0);
|
|
77
78
|
const rollUp = ref(false);
|
|
78
79
|
const searchString = ref('');
|
|
@@ -115,8 +116,9 @@ function onItemActivated(item) {
|
|
|
115
116
|
|
|
116
117
|
async function open(event, element = null) {
|
|
117
118
|
isOpen.value = true;
|
|
118
|
-
pageX
|
|
119
|
-
|
|
119
|
+
pageX = element ? element.getBoundingClientRect().left : event.pageX;
|
|
120
|
+
targetBottom = element ? element.getBoundingClientRect().bottom : event.pageY;
|
|
121
|
+
targetTop = element ? element.getBoundingClientRect().top : event.pageY;
|
|
120
122
|
offsetY.value = element ? (element.getBoundingClientRect().height + 2) : 0; // offset in case we roll up
|
|
121
123
|
forElement.value = element;
|
|
122
124
|
|
|
@@ -183,6 +185,9 @@ function onBackdrop(event) {
|
|
|
183
185
|
|
|
184
186
|
function close() {
|
|
185
187
|
isOpen.value = false;
|
|
188
|
+
container.value.style.maxHeight = 'unset';
|
|
189
|
+
container.value.style.bottom = 'unset';
|
|
190
|
+
container.value.style.top = 'unset';
|
|
186
191
|
container.value = null;
|
|
187
192
|
emit('close');
|
|
188
193
|
}
|
|
@@ -192,12 +197,14 @@ function position() {
|
|
|
192
197
|
|
|
193
198
|
const size = getHiddenElementSize(container.value);
|
|
194
199
|
|
|
195
|
-
let left = pageX
|
|
196
|
-
let top =
|
|
200
|
+
let left = pageX;
|
|
201
|
+
let top = targetBottom + 1;
|
|
197
202
|
let width = container.value.offsetParent ? container.value.offsetWidth : size.width;
|
|
198
203
|
let height = container.value.offsetParent ? container.value.offsetHeight : size.height;
|
|
199
204
|
let viewport = getViewport();
|
|
200
205
|
|
|
206
|
+
let bottom = viewport.height - targetTop + 1;
|
|
207
|
+
|
|
201
208
|
//flip
|
|
202
209
|
if (left + width - document.body.scrollLeft > viewport.width) {
|
|
203
210
|
// if this is like a dropdown right align instead of flip
|
|
@@ -210,8 +217,11 @@ function position() {
|
|
|
210
217
|
|
|
211
218
|
//flip
|
|
212
219
|
if (top + height - document.body.scrollTop > viewport.height) {
|
|
213
|
-
top
|
|
214
|
-
|
|
220
|
+
if (top - document.body.scrollTop > viewport.height/2) {
|
|
221
|
+
rollUp.value = true;
|
|
222
|
+
} else {
|
|
223
|
+
rollUp.value = false;
|
|
224
|
+
}
|
|
215
225
|
} else {
|
|
216
226
|
rollUp.value = false;
|
|
217
227
|
}
|
|
@@ -221,13 +231,17 @@ function position() {
|
|
|
221
231
|
left = document.body.scrollLeft;
|
|
222
232
|
}
|
|
223
233
|
|
|
224
|
-
//fit
|
|
225
|
-
if (top < document.body.scrollTop) {
|
|
226
|
-
top = document.body.scrollTop;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
234
|
container.value.style.left = left + 'px';
|
|
230
|
-
|
|
235
|
+
if (rollUp.value) {
|
|
236
|
+
container.value.style.top = 'unset';
|
|
237
|
+
container.value.style.bottom = bottom + 'px';
|
|
238
|
+
container.value.style.maxHeight = viewport.height - bottom + 'px';
|
|
239
|
+
container.value.style.height = 'auto';
|
|
240
|
+
} else {
|
|
241
|
+
container.value.style.top = top + 'px';
|
|
242
|
+
container.value.style.bottom = 'unset';
|
|
243
|
+
container.value.style.maxHeight = viewport.height - top + 'px';
|
|
244
|
+
}
|
|
231
245
|
}
|
|
232
246
|
|
|
233
247
|
defineExpose({
|
|
@@ -271,7 +285,7 @@ defineExpose({
|
|
|
271
285
|
z-index: 3001;
|
|
272
286
|
color: var(--pankow-color-dark);
|
|
273
287
|
overflow: auto;
|
|
274
|
-
max-height: 100
|
|
288
|
+
/*max-height: 100%;*/
|
|
275
289
|
outline: none;
|
|
276
290
|
}
|
|
277
291
|
|
package/components/MenuItem.vue
CHANGED
|
@@ -54,7 +54,7 @@ function onActivated() {
|
|
|
54
54
|
display: flex;
|
|
55
55
|
align-items: center;
|
|
56
56
|
user-select: none;
|
|
57
|
-
padding:
|
|
57
|
+
padding: 6px 10px;
|
|
58
58
|
cursor: pointer;
|
|
59
59
|
font-size: 14px; /* this also defines the overall widget size as all sizes are in rem */
|
|
60
60
|
font-family: var(--font-family);
|
package/gallery/Index.vue
CHANGED
|
@@ -1,3 +1,326 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
|
|
3
|
+
import { ref, useTemplateRef, onMounted } from 'vue';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
ButtonGroup,
|
|
8
|
+
Dialog,
|
|
9
|
+
EmailInput,
|
|
10
|
+
InputDialog,
|
|
11
|
+
MainLayout,
|
|
12
|
+
Notification,
|
|
13
|
+
NumberInput,
|
|
14
|
+
TextInput,
|
|
15
|
+
PasswordInput,
|
|
16
|
+
Icon,
|
|
17
|
+
Breadcrumb,
|
|
18
|
+
Checkbox,
|
|
19
|
+
Radiobutton,
|
|
20
|
+
OfflineBanner,
|
|
21
|
+
Popover,
|
|
22
|
+
ProgressBar,
|
|
23
|
+
SideBar,
|
|
24
|
+
TabView,
|
|
25
|
+
TableView,
|
|
26
|
+
TagInput,
|
|
27
|
+
Menu,
|
|
28
|
+
SingleSelect,
|
|
29
|
+
MultiSelect,
|
|
30
|
+
Spinner,
|
|
31
|
+
Switch,
|
|
32
|
+
FormGroup,
|
|
33
|
+
InputGroup,
|
|
34
|
+
} from '../index.js';
|
|
35
|
+
|
|
36
|
+
import { prettyDate, prettyLongDate } from '../utils.js';
|
|
37
|
+
|
|
38
|
+
import DirectoryViewDemo from './DirectoryViewDemo.vue';
|
|
39
|
+
import CustomMenuItem from './CustomMenuItem.vue';
|
|
40
|
+
|
|
41
|
+
const dateTime = ref('2024-05-01T01:13');
|
|
42
|
+
const buttonTypeToggle = ref(false);
|
|
43
|
+
const largeDialog = ref(false);
|
|
44
|
+
const checkbox1Enabled = ref(true);
|
|
45
|
+
const checkbox2Enabled = ref(false);
|
|
46
|
+
const switch1 = ref(true);
|
|
47
|
+
const switch2 = ref(false);
|
|
48
|
+
const offlineBannerActive = ref(false);
|
|
49
|
+
const textInputValue = ref('');
|
|
50
|
+
const textInputErrorValue = ref('');
|
|
51
|
+
const textInputDisabledValue = ref('do not edit');
|
|
52
|
+
const passwordInputValue = ref('');
|
|
53
|
+
const passwordInputDisabledValue = ref('something');
|
|
54
|
+
const emailInputValue = ref('');
|
|
55
|
+
const numberInputValue = ref(0);
|
|
56
|
+
const numberInputDisabledValue = ref(1337);
|
|
57
|
+
const tagInputValue = ref([ 'pecan', 'walnut' ]);
|
|
58
|
+
const radiobutton = ref('one');
|
|
59
|
+
const progressValue = ref(44);
|
|
60
|
+
|
|
61
|
+
const breadcrumbHomeItem = {
|
|
62
|
+
label: 'Home',
|
|
63
|
+
icon: 'fa-solid fa-house',
|
|
64
|
+
route: '#'
|
|
65
|
+
};
|
|
66
|
+
const breadcrumbItems = [{
|
|
67
|
+
icon: '',
|
|
68
|
+
label: 'Cloudron',
|
|
69
|
+
action: () => { console.log('Breadcrumb Cloudron'); }
|
|
70
|
+
}, {
|
|
71
|
+
icon: 'fa-solid fa-user-astronaut',
|
|
72
|
+
label: 'Founders',
|
|
73
|
+
action: () => { console.log('Breadcrumb Founders'); }
|
|
74
|
+
}, {
|
|
75
|
+
icon: 'fa-regular fa-file-pdf',
|
|
76
|
+
label: 'Documents',
|
|
77
|
+
action: () => { console.log('Breadcrumb Documents'); }
|
|
78
|
+
}, {
|
|
79
|
+
icon: '',
|
|
80
|
+
label: 'Tax',
|
|
81
|
+
action: () => { console.log('Breadcrumb Tax'); }
|
|
82
|
+
}];
|
|
83
|
+
const singleselectModel = [{
|
|
84
|
+
display: 'Pizza',
|
|
85
|
+
id: 'pizza'
|
|
86
|
+
}, {
|
|
87
|
+
display: 'Pasta',
|
|
88
|
+
id: 'pasta'
|
|
89
|
+
}, {
|
|
90
|
+
display: 'Pizza',
|
|
91
|
+
id: 'pizza2'
|
|
92
|
+
}, {
|
|
93
|
+
display: 'Falafel',
|
|
94
|
+
id: 'falafel'
|
|
95
|
+
}, {
|
|
96
|
+
display: 'Menemen',
|
|
97
|
+
id: 'menemen'
|
|
98
|
+
},{
|
|
99
|
+
display: 'Pizza',
|
|
100
|
+
id: 'pizza11'
|
|
101
|
+
}, {
|
|
102
|
+
display: 'Pasta',
|
|
103
|
+
id: 'pasta11'
|
|
104
|
+
}, {
|
|
105
|
+
display: 'Pizza',
|
|
106
|
+
id: 'pizza211'
|
|
107
|
+
}, {
|
|
108
|
+
display: 'Falafel',
|
|
109
|
+
id: 'falafel11'
|
|
110
|
+
}, {
|
|
111
|
+
display: 'Menemen',
|
|
112
|
+
id: 'menemen11'
|
|
113
|
+
},{
|
|
114
|
+
display: 'Pizza',
|
|
115
|
+
id: 'pizza22'
|
|
116
|
+
}, {
|
|
117
|
+
display: 'Pasta',
|
|
118
|
+
id: 'pasta22'
|
|
119
|
+
}, {
|
|
120
|
+
display: 'Pizza',
|
|
121
|
+
id: 'pizza222'
|
|
122
|
+
}, {
|
|
123
|
+
display: 'Falafel',
|
|
124
|
+
id: 'falafel22'
|
|
125
|
+
}, {
|
|
126
|
+
display: 'Menemen',
|
|
127
|
+
id: 'menemen22'
|
|
128
|
+
}];
|
|
129
|
+
const singleselectValue = ref({
|
|
130
|
+
display: 'Pasta',
|
|
131
|
+
id: 'pasta'
|
|
132
|
+
});
|
|
133
|
+
const singleselectValueWithKey = ref('falafel');
|
|
134
|
+
const singleselectValueNoInit = ref('');
|
|
135
|
+
const multiselectModel = [{
|
|
136
|
+
display: 'Pizza',
|
|
137
|
+
id: 'pizza'
|
|
138
|
+
}, {
|
|
139
|
+
display: 'Pasta',
|
|
140
|
+
id: 'pasta'
|
|
141
|
+
}, {
|
|
142
|
+
separator: true,
|
|
143
|
+
}, {
|
|
144
|
+
display: 'Pizza',
|
|
145
|
+
id: 'pizza2'
|
|
146
|
+
}, {
|
|
147
|
+
display: 'Falafel',
|
|
148
|
+
id: 'falafel'
|
|
149
|
+
}, {
|
|
150
|
+
display: 'Menemen',
|
|
151
|
+
id: 'menemen'
|
|
152
|
+
}];
|
|
153
|
+
const multiselectValue = ref([]);
|
|
154
|
+
const multiselectValueWithCustomLabel = ref([]);
|
|
155
|
+
const multiselectValueWithOptionKey = ref([ 'falafel' ]);
|
|
156
|
+
const multiselectValueWithSelectAll = ref([]);
|
|
157
|
+
const menuModelItemsSingle = [{
|
|
158
|
+
label: 'Single Item',
|
|
159
|
+
action: () => { console.log('Single Menu Item'); }
|
|
160
|
+
}];
|
|
161
|
+
const menuModelItems = [{
|
|
162
|
+
label: 'Item 1',
|
|
163
|
+
action: () => { console.log('Menu Item 1 clicked'); }
|
|
164
|
+
}, {
|
|
165
|
+
label: 'Item 2',
|
|
166
|
+
action: () => { console.log('Menu Item 2 clicked'); }
|
|
167
|
+
}, {
|
|
168
|
+
separator: true
|
|
169
|
+
}, {
|
|
170
|
+
label: 'Item 3',
|
|
171
|
+
action: () => { console.log('Menu Item 3 clicked'); }
|
|
172
|
+
}];
|
|
173
|
+
const menuModelItemsWithIcons = [{
|
|
174
|
+
icon: 'fa-solid fa-circle-notch fa-spin',
|
|
175
|
+
label: 'Item 1',
|
|
176
|
+
action: () => { console.log('Menu Item 1 clicked'); }
|
|
177
|
+
}, {
|
|
178
|
+
icon: '',
|
|
179
|
+
label: 'Item 2',
|
|
180
|
+
checkbox: true,
|
|
181
|
+
action: () => { console.log('Menu Item 2 clicked'); }
|
|
182
|
+
}, {
|
|
183
|
+
separator: true
|
|
184
|
+
}, {
|
|
185
|
+
href: 'https://cloudron.io',
|
|
186
|
+
target: '_blank',
|
|
187
|
+
label: 'Item as link',
|
|
188
|
+
}, {
|
|
189
|
+
separator: true
|
|
190
|
+
}, {
|
|
191
|
+
icon: 'fa-regular fa-file-pdf',
|
|
192
|
+
label: 'Item 3',
|
|
193
|
+
action: () => { console.log('Menu Item 3 clicked'); }
|
|
194
|
+
}];
|
|
195
|
+
const menuModelWithDOMElements = [{
|
|
196
|
+
label: 'Normal Item',
|
|
197
|
+
action: () => { console.log('Normal Item clicked'); }
|
|
198
|
+
}, {
|
|
199
|
+
type: CustomMenuItem,
|
|
200
|
+
action: () => { console.log('Large Item clicked'); }
|
|
201
|
+
}, {
|
|
202
|
+
label: 'This is a link',
|
|
203
|
+
href: 'https://cloudron.io',
|
|
204
|
+
target: '_blank',
|
|
205
|
+
}];
|
|
206
|
+
const notificationPosition = ref('top-center');
|
|
207
|
+
const tableColumns = {
|
|
208
|
+
status: {
|
|
209
|
+
label: 'Status',
|
|
210
|
+
sort(a, b) {
|
|
211
|
+
return a.index < b.index ? -1 : 1;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
filename: {
|
|
215
|
+
label: 'File name',
|
|
216
|
+
icon: 'fa-solid fa-lock',
|
|
217
|
+
sort: true,
|
|
218
|
+
},
|
|
219
|
+
extra: {
|
|
220
|
+
label: 'Extra',
|
|
221
|
+
hideMobile: true,
|
|
222
|
+
},
|
|
223
|
+
action: {
|
|
224
|
+
label: 'Actions',
|
|
225
|
+
sort: false
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const tableModel = ref([]);
|
|
229
|
+
const tabs = {
|
|
230
|
+
tab0: 'Fruit',
|
|
231
|
+
tab1: 'Veggies',
|
|
232
|
+
tab2: 'Meat',
|
|
233
|
+
tab3: 'Drinks',
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const myDialog = useTemplateRef('myDialog');
|
|
237
|
+
function onButtonClick() {
|
|
238
|
+
myDialog.value.open();
|
|
239
|
+
console.log('Button clicked');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function onPopover(popover, event, elem = null) {
|
|
243
|
+
popover.open(event, elem);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const myInputDialog = useTemplateRef('myInputDialog');
|
|
247
|
+
async function onAskForConfirmation() {
|
|
248
|
+
console.log('User answered with:', await myInputDialog.value.confirm({
|
|
249
|
+
message: 'Really delete this or that?',
|
|
250
|
+
confirmStyle: 'danger',
|
|
251
|
+
confirmLabel: 'yeah sure',
|
|
252
|
+
rejectLabel: 'hm no'
|
|
253
|
+
}) ? 'ok' : 'nay');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function onAskForString() {
|
|
257
|
+
console.log('User answered with:', await myInputDialog.value.prompt({
|
|
258
|
+
message: 'New Filename',
|
|
259
|
+
value: 'newfile.txt',
|
|
260
|
+
confirmStyle: 'success',
|
|
261
|
+
confirmLabel: 'Save',
|
|
262
|
+
rejectLabel: 'Close'
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function onOpenMenu(menu, event, elem = null) {
|
|
267
|
+
menu.open(event, elem);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const sideBar = useTemplateRef('sideBar');
|
|
271
|
+
function onSideBarEntry() {
|
|
272
|
+
sideBar.value.close();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function onRaiseNotification(text, type) {
|
|
276
|
+
if (type) window.pankow.notify({ text, type });
|
|
277
|
+
else window.pankow.notify(text);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function onRaisePersistentNotification(text) {
|
|
281
|
+
window.pankow.notify({
|
|
282
|
+
text,
|
|
283
|
+
persistent: true
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const tabview = useTemplateRef('tabview');
|
|
288
|
+
function onChangeTab(tab) {
|
|
289
|
+
tabview.value.open(tab);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function onSwitchChange(value) {
|
|
293
|
+
console.log('switch changed to', value);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
onMounted(async () => {
|
|
297
|
+
console.log('Pankow widget gallery');
|
|
298
|
+
|
|
299
|
+
for (let i = 0; i < 100; ++i) {
|
|
300
|
+
tableModel.value.push({
|
|
301
|
+
status: {
|
|
302
|
+
label: i + ' some state',
|
|
303
|
+
index: i
|
|
304
|
+
},
|
|
305
|
+
filename: `Test file ${i}.txt`,
|
|
306
|
+
extra: 'Only visible on desktop',
|
|
307
|
+
action: 'foo'
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
document.getElementById('borderRadiusInput').addEventListener('change', (event) => {
|
|
312
|
+
let root = document.documentElement;
|
|
313
|
+
root.style.setProperty('--pankow-border-radius', event.target.value + 'px');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
setTimeout(() => {
|
|
317
|
+
radiobutton.value = 'two';
|
|
318
|
+
singleselectValueWithKey.value = 'pizza';
|
|
319
|
+
}, 2000 );
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
</script>
|
|
323
|
+
|
|
1
324
|
<template>
|
|
2
325
|
|
|
3
326
|
<Notification :position="notificationPosition"/>
|
|
@@ -42,8 +365,8 @@
|
|
|
42
365
|
</SideBar>
|
|
43
366
|
<MainLayout>
|
|
44
367
|
<template #dialogs>
|
|
45
|
-
<Dialog ref="myDialog" title="My Dialog" :
|
|
46
|
-
This is a
|
|
368
|
+
<Dialog ref="myDialog" title="My Dialog" :show-x="true" rejectLabel="Done">
|
|
369
|
+
This is a dialog.
|
|
47
370
|
<Button @click="largeDialog = !largeDialog">Large/auto</Button>
|
|
48
371
|
<div :style="{ width: largeDialog ? '720px' : 'auto', height: largeDialog ? '400px' : 'auto' }"></div>
|
|
49
372
|
</Dialog>
|
|
@@ -73,8 +396,10 @@
|
|
|
73
396
|
</div>
|
|
74
397
|
|
|
75
398
|
<div>
|
|
76
|
-
<h2>utils
|
|
77
|
-
<
|
|
399
|
+
<h2>utils pretty functions:</h2>
|
|
400
|
+
<input type="datetime-local" v-model="dateTime"/>
|
|
401
|
+
<p>prettyDate(): {{ prettyDate(dateTime) }}</p>
|
|
402
|
+
<p>prettyLongDate(): {{ prettyLongDate(dateTime) }}</p>
|
|
78
403
|
</div>
|
|
79
404
|
|
|
80
405
|
<h2 id="button">Button</h2>
|
|
@@ -281,8 +606,7 @@
|
|
|
281
606
|
|
|
282
607
|
<h2 id="dialog">Dialog</h2>
|
|
283
608
|
<div style="display: flex; gap: 10px">
|
|
284
|
-
<Button @click="onButtonClick(
|
|
285
|
-
<Button @click="onButtonClick(true)">Open Modal Dialog</Button>
|
|
609
|
+
<Button @click="onButtonClick()">Open Dialog</Button>
|
|
286
610
|
<Button @click="onAskForConfirmation()">Confirm</Button>
|
|
287
611
|
<Button @click="onAskForString()">Prompt</Button>
|
|
288
612
|
</div>
|
|
@@ -408,330 +732,6 @@
|
|
|
408
732
|
|
|
409
733
|
</template>
|
|
410
734
|
|
|
411
|
-
<script>
|
|
412
|
-
|
|
413
|
-
'use strict';
|
|
414
|
-
|
|
415
|
-
import {
|
|
416
|
-
Button,
|
|
417
|
-
ButtonGroup,
|
|
418
|
-
Dialog,
|
|
419
|
-
EmailInput,
|
|
420
|
-
InputDialog,
|
|
421
|
-
MainLayout,
|
|
422
|
-
Notification,
|
|
423
|
-
NumberInput,
|
|
424
|
-
TextInput,
|
|
425
|
-
PasswordInput,
|
|
426
|
-
Icon,
|
|
427
|
-
Breadcrumb,
|
|
428
|
-
Checkbox,
|
|
429
|
-
Radiobutton,
|
|
430
|
-
OfflineBanner,
|
|
431
|
-
Popover,
|
|
432
|
-
ProgressBar,
|
|
433
|
-
SideBar,
|
|
434
|
-
TabView,
|
|
435
|
-
TableView,
|
|
436
|
-
TagInput,
|
|
437
|
-
Menu,
|
|
438
|
-
SingleSelect,
|
|
439
|
-
MultiSelect,
|
|
440
|
-
Spinner,
|
|
441
|
-
Switch,
|
|
442
|
-
FormGroup,
|
|
443
|
-
InputGroup,
|
|
444
|
-
} from '../index.js';
|
|
445
|
-
|
|
446
|
-
import { prettyDate } from '../utils.js';
|
|
447
|
-
|
|
448
|
-
import DirectoryViewDemo from './DirectoryViewDemo.vue';
|
|
449
|
-
import CustomMenuItem from './CustomMenuItem.vue';
|
|
450
|
-
|
|
451
|
-
export default {
|
|
452
|
-
name: 'WidgetGallery',
|
|
453
|
-
components: {
|
|
454
|
-
CustomMenuItem,
|
|
455
|
-
Spinner,
|
|
456
|
-
Button,
|
|
457
|
-
ButtonGroup,
|
|
458
|
-
Dialog,
|
|
459
|
-
DirectoryViewDemo,
|
|
460
|
-
EmailInput,
|
|
461
|
-
InputDialog,
|
|
462
|
-
MainLayout,
|
|
463
|
-
TextInput,
|
|
464
|
-
PasswordInput,
|
|
465
|
-
Icon,
|
|
466
|
-
Breadcrumb,
|
|
467
|
-
Checkbox,
|
|
468
|
-
Radiobutton,
|
|
469
|
-
OfflineBanner,
|
|
470
|
-
Popover,
|
|
471
|
-
ProgressBar,
|
|
472
|
-
SideBar,
|
|
473
|
-
Menu,
|
|
474
|
-
MultiSelect,
|
|
475
|
-
SingleSelect,
|
|
476
|
-
Notification,
|
|
477
|
-
NumberInput,
|
|
478
|
-
TabView,
|
|
479
|
-
TableView,
|
|
480
|
-
TagInput,
|
|
481
|
-
Switch,
|
|
482
|
-
FormGroup,
|
|
483
|
-
InputGroup,
|
|
484
|
-
},
|
|
485
|
-
data() {
|
|
486
|
-
return {
|
|
487
|
-
dateTime: '2024-05-01T01:13',
|
|
488
|
-
buttonTypeToggle: false,
|
|
489
|
-
largeDialog: false,
|
|
490
|
-
modalIsDialog: false,
|
|
491
|
-
showXInDialog: false,
|
|
492
|
-
checkbox1Enabled: true,
|
|
493
|
-
checkbox2Enabled: false,
|
|
494
|
-
switch1: true,
|
|
495
|
-
switch2: false,
|
|
496
|
-
offlineBannerActive: false,
|
|
497
|
-
textInputValue: '',
|
|
498
|
-
textInputErrorValue: '',
|
|
499
|
-
textInputDisabledValue: 'do not edit',
|
|
500
|
-
passwordInputValue: '',
|
|
501
|
-
passwordInputDisabledValue: 'something',
|
|
502
|
-
emailInputValue: '',
|
|
503
|
-
numberInputValue: 0,
|
|
504
|
-
numberInputDisabledValue: 1337,
|
|
505
|
-
tagInputValue: [ 'pecan', 'walnut' ],
|
|
506
|
-
radiobutton: 'one',
|
|
507
|
-
progressValue: 44,
|
|
508
|
-
breadcrumbHomeItem: {
|
|
509
|
-
label: 'Home',
|
|
510
|
-
icon: 'fa-solid fa-house',
|
|
511
|
-
route: '#'
|
|
512
|
-
},
|
|
513
|
-
breadcrumbItems: [{
|
|
514
|
-
icon: '',
|
|
515
|
-
label: 'Cloudron',
|
|
516
|
-
action: () => { console.log('Breadcrumb Cloudron'); }
|
|
517
|
-
}, {
|
|
518
|
-
icon: 'fa-solid fa-user-astronaut',
|
|
519
|
-
label: 'Founders',
|
|
520
|
-
action: () => { console.log('Breadcrumb Founders'); }
|
|
521
|
-
}, {
|
|
522
|
-
icon: 'fa-regular fa-file-pdf',
|
|
523
|
-
label: 'Documents',
|
|
524
|
-
action: () => { console.log('Breadcrumb Documents'); }
|
|
525
|
-
}, {
|
|
526
|
-
icon: '',
|
|
527
|
-
label: 'Tax',
|
|
528
|
-
action: () => { console.log('Breadcrumb Tax'); }
|
|
529
|
-
}],
|
|
530
|
-
singleselectModel: [{
|
|
531
|
-
display: 'Pizza',
|
|
532
|
-
id: 'pizza'
|
|
533
|
-
}, {
|
|
534
|
-
display: 'Pasta',
|
|
535
|
-
id: 'pasta'
|
|
536
|
-
}, {
|
|
537
|
-
display: 'Pizza',
|
|
538
|
-
id: 'pizza2'
|
|
539
|
-
}, {
|
|
540
|
-
display: 'Falafel',
|
|
541
|
-
id: 'falafel'
|
|
542
|
-
}, {
|
|
543
|
-
display: 'Menemen',
|
|
544
|
-
id: 'menemen'
|
|
545
|
-
}],
|
|
546
|
-
singleselectValue: {
|
|
547
|
-
display: 'Pasta',
|
|
548
|
-
id: 'pasta'
|
|
549
|
-
},
|
|
550
|
-
singleselectValueWithKey: 'falafel',
|
|
551
|
-
singleselectValueNoInit: '',
|
|
552
|
-
multiselectModel: [{
|
|
553
|
-
display: 'Pizza',
|
|
554
|
-
id: 'pizza'
|
|
555
|
-
}, {
|
|
556
|
-
display: 'Pasta',
|
|
557
|
-
id: 'pasta'
|
|
558
|
-
}, {
|
|
559
|
-
separator: true,
|
|
560
|
-
}, {
|
|
561
|
-
display: 'Pizza',
|
|
562
|
-
id: 'pizza2'
|
|
563
|
-
}, {
|
|
564
|
-
display: 'Falafel',
|
|
565
|
-
id: 'falafel'
|
|
566
|
-
}, {
|
|
567
|
-
display: 'Menemen',
|
|
568
|
-
id: 'menemen'
|
|
569
|
-
}],
|
|
570
|
-
multiselectValue: [],
|
|
571
|
-
multiselectValueWithCustomLabel: [],
|
|
572
|
-
multiselectValueWithOptionKey: [ 'falafel' ],
|
|
573
|
-
multiselectValueWithSelectAll: [],
|
|
574
|
-
menuModelItemsSingle: [{
|
|
575
|
-
label: 'Single Item',
|
|
576
|
-
action: () => { console.log('Single Menu Item'); }
|
|
577
|
-
}],
|
|
578
|
-
menuModelItems: [{
|
|
579
|
-
label: 'Item 1',
|
|
580
|
-
action: () => { console.log('Menu Item 1 clicked'); }
|
|
581
|
-
}, {
|
|
582
|
-
label: 'Item 2',
|
|
583
|
-
action: () => { console.log('Menu Item 2 clicked'); }
|
|
584
|
-
}, {
|
|
585
|
-
separator: true
|
|
586
|
-
}, {
|
|
587
|
-
label: 'Item 3',
|
|
588
|
-
action: () => { console.log('Menu Item 3 clicked'); }
|
|
589
|
-
}],
|
|
590
|
-
menuModelItemsWithIcons: [{
|
|
591
|
-
icon: 'fa-solid fa-circle-notch fa-spin',
|
|
592
|
-
label: 'Item 1',
|
|
593
|
-
action: () => { console.log('Menu Item 1 clicked'); }
|
|
594
|
-
}, {
|
|
595
|
-
icon: '',
|
|
596
|
-
label: 'Item 2',
|
|
597
|
-
checkbox: true,
|
|
598
|
-
action: () => { console.log('Menu Item 2 clicked'); }
|
|
599
|
-
}, {
|
|
600
|
-
separator: true
|
|
601
|
-
}, {
|
|
602
|
-
href: 'https://cloudron.io',
|
|
603
|
-
target: '_blank',
|
|
604
|
-
label: 'Item as link',
|
|
605
|
-
}, {
|
|
606
|
-
separator: true
|
|
607
|
-
}, {
|
|
608
|
-
icon: 'fa-regular fa-file-pdf',
|
|
609
|
-
label: 'Item 3',
|
|
610
|
-
action: () => { console.log('Menu Item 3 clicked'); }
|
|
611
|
-
}],
|
|
612
|
-
menuModelWithDOMElements: [{
|
|
613
|
-
label: 'Normal Item',
|
|
614
|
-
action: () => { console.log('Normal Item clicked'); }
|
|
615
|
-
}, {
|
|
616
|
-
type: CustomMenuItem,
|
|
617
|
-
action: () => { console.log('Large Item clicked'); }
|
|
618
|
-
}, {
|
|
619
|
-
label: 'This is a link',
|
|
620
|
-
href: 'https://cloudron.io',
|
|
621
|
-
target: '_blank',
|
|
622
|
-
}],
|
|
623
|
-
notificationPosition: 'top-center',
|
|
624
|
-
tableColumns: {
|
|
625
|
-
status: {
|
|
626
|
-
label: 'Status',
|
|
627
|
-
sort(a, b) {
|
|
628
|
-
return a.index < b.index ? -1 : 1;
|
|
629
|
-
}
|
|
630
|
-
},
|
|
631
|
-
filename: {
|
|
632
|
-
label: 'File name',
|
|
633
|
-
icon: 'fa-solid fa-lock',
|
|
634
|
-
sort: true,
|
|
635
|
-
},
|
|
636
|
-
extra: {
|
|
637
|
-
label: 'Extra',
|
|
638
|
-
hideMobile: true,
|
|
639
|
-
},
|
|
640
|
-
action: {
|
|
641
|
-
label: 'Actions',
|
|
642
|
-
sort: false
|
|
643
|
-
}
|
|
644
|
-
},
|
|
645
|
-
tableModel: [],
|
|
646
|
-
tabs: {
|
|
647
|
-
tab0: 'Fruit',
|
|
648
|
-
tab1: 'Veggies',
|
|
649
|
-
tab2: 'Meat',
|
|
650
|
-
tab3: 'Drinks',
|
|
651
|
-
}
|
|
652
|
-
};
|
|
653
|
-
},
|
|
654
|
-
methods: {
|
|
655
|
-
prettyDate,
|
|
656
|
-
onButtonClick(modal) {
|
|
657
|
-
this.modalIsDialog = modal;
|
|
658
|
-
this.showXInDialog = !modal;
|
|
659
|
-
this.$refs.myDialog.open();
|
|
660
|
-
console.log('Button clicked');
|
|
661
|
-
},
|
|
662
|
-
onPopover(popover, event, elem = null) {
|
|
663
|
-
popover.open(event, elem);
|
|
664
|
-
},
|
|
665
|
-
async onAskForConfirmation() {
|
|
666
|
-
console.log('User answered with:', await this.$refs.myInputDialog.confirm({
|
|
667
|
-
message: 'Really delete this or that?',
|
|
668
|
-
confirmStyle: 'danger',
|
|
669
|
-
confirmLabel: 'yeah sure',
|
|
670
|
-
rejectLabel: 'hm no'
|
|
671
|
-
}) ? 'ok' : 'nay');
|
|
672
|
-
},
|
|
673
|
-
async onAskForString() {
|
|
674
|
-
console.log('User answered with:', await this.$refs.myInputDialog.prompt({
|
|
675
|
-
message: 'New Filename',
|
|
676
|
-
modal: false,
|
|
677
|
-
value: 'newfile.txt',
|
|
678
|
-
confirmStyle: 'success',
|
|
679
|
-
confirmLabel: 'Save',
|
|
680
|
-
rejectLabel: 'Close'
|
|
681
|
-
}));
|
|
682
|
-
},
|
|
683
|
-
onOpenMenu(menu, event, elem = null) {
|
|
684
|
-
menu.open(event, elem);
|
|
685
|
-
},
|
|
686
|
-
onSideBarEntry() {
|
|
687
|
-
this.$refs.sideBar.close();
|
|
688
|
-
},
|
|
689
|
-
onRaiseNotification(text, type) {
|
|
690
|
-
if (type) window.pankow.notify({ text, type });
|
|
691
|
-
else window.pankow.notify(text);
|
|
692
|
-
},
|
|
693
|
-
onRaisePersistentNotification(text) {
|
|
694
|
-
window.pankow.notify({
|
|
695
|
-
text,
|
|
696
|
-
persistent: true
|
|
697
|
-
});
|
|
698
|
-
},
|
|
699
|
-
onChangeTab(tab) {
|
|
700
|
-
this.$refs.tabview.open(tab);
|
|
701
|
-
},
|
|
702
|
-
onSwitchChange(value) {
|
|
703
|
-
console.log('switch changed to', value);
|
|
704
|
-
},
|
|
705
|
-
},
|
|
706
|
-
async mounted() {
|
|
707
|
-
console.log('Pankow widget gallery');
|
|
708
|
-
|
|
709
|
-
for (let i = 0; i < 100; ++i) {
|
|
710
|
-
this.tableModel.push({
|
|
711
|
-
status: {
|
|
712
|
-
label: i + ' some state',
|
|
713
|
-
index: i
|
|
714
|
-
},
|
|
715
|
-
filename: `Test file ${i}.txt`,
|
|
716
|
-
extra: 'Only visible on desktop',
|
|
717
|
-
action: 'foo'
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
document.getElementById('borderRadiusInput').addEventListener('change', (event) => {
|
|
722
|
-
let root = document.documentElement;
|
|
723
|
-
root.style.setProperty('--pankow-border-radius', event.target.value + 'px');
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
setTimeout(() => {
|
|
727
|
-
this.radiobutton = 'two';
|
|
728
|
-
this.singleselectValueWithKey = 'pizza';
|
|
729
|
-
}, 2000 );
|
|
730
|
-
}
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
</script>
|
|
734
|
-
|
|
735
735
|
<style>
|
|
736
736
|
|
|
737
737
|
#app {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudron/pankow",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.2.
|
|
4
|
+
"version": "3.2.2",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"@fontsource/inter": "^5.2.6",
|
|
16
16
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
|
17
17
|
"filesize": "^11.0.1",
|
|
18
|
-
"moment": "^2.30.1",
|
|
19
18
|
"monaco-editor": "^0.52.2"
|
|
20
19
|
},
|
|
21
20
|
"devDependencies": {
|
package/utils.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { filesize } from 'filesize';
|
|
3
3
|
import { customRef } from 'vue';
|
|
4
|
-
import moment from 'moment';
|
|
5
4
|
|
|
6
5
|
// https://vuejs.org/api/reactivity-advanced.html#customref
|
|
7
6
|
function useDebouncedRef(value, delay = 300) {
|
|
@@ -69,17 +68,55 @@ function prettyDecimalSize(size, fallback) {
|
|
|
69
68
|
return (size / Math.pow(1000, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
|
|
70
69
|
}
|
|
71
70
|
|
|
71
|
+
function fromNow(date) {
|
|
72
|
+
const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
|
|
73
|
+
|
|
74
|
+
const now = new Date();
|
|
75
|
+
const diff = date - now;
|
|
76
|
+
|
|
77
|
+
const seconds = Math.round(diff / 1000);
|
|
78
|
+
const minutes = Math.round(diff / (1000 * 60));
|
|
79
|
+
const hours = Math.round(diff / (1000 * 60 * 60));
|
|
80
|
+
const days = Math.round(diff / (1000 * 60 * 60 * 24));
|
|
81
|
+
const weeks = Math.round(diff / (1000 * 60 * 60 * 24 * 7));
|
|
82
|
+
const months = Math.round(diff / (1000 * 60 * 60 * 24 * 30));
|
|
83
|
+
const years = Math.round(diff / (1000 * 60 * 60 * 24 * 365));
|
|
84
|
+
|
|
85
|
+
// Pick the best unit based on the magnitude
|
|
86
|
+
if (Math.abs(seconds) < 60) return rtf.format(seconds, 'second');
|
|
87
|
+
if (Math.abs(minutes) < 60) return rtf.format(minutes, 'minute');
|
|
88
|
+
if (Math.abs(hours) < 24) return rtf.format(hours, 'hour');
|
|
89
|
+
if (Math.abs(days) < 7) return rtf.format(days, 'day');
|
|
90
|
+
if (Math.abs(weeks) < 4) return rtf.format(weeks, 'week');
|
|
91
|
+
if (Math.abs(months) < 12) return rtf.format(months, 'month');
|
|
92
|
+
|
|
93
|
+
return rtf.format(years, 'year');
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
// this will print a human friendly datetime offset from now
|
|
73
97
|
function prettyDate(value) {
|
|
74
98
|
if (!value) return 'never';
|
|
75
|
-
|
|
99
|
+
|
|
100
|
+
const date = new Date(value);
|
|
101
|
+
if (isNaN(date.getTime())) return 'unkown';
|
|
102
|
+
|
|
103
|
+
return fromNow(date);
|
|
76
104
|
}
|
|
77
105
|
|
|
78
106
|
function prettyLongDate(value) {
|
|
79
107
|
if (!value) return 'unknown';
|
|
80
108
|
|
|
81
109
|
const date = new Date(value);
|
|
82
|
-
|
|
110
|
+
if (isNaN(date.getTime())) return 'unknown';
|
|
111
|
+
|
|
112
|
+
const formatter = new Intl.DateTimeFormat(undefined, {
|
|
113
|
+
dateStyle: 'long',
|
|
114
|
+
timeStyle: 'medium'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const formattedDate = formatter.format(date);
|
|
118
|
+
|
|
119
|
+
return formattedDate;
|
|
83
120
|
}
|
|
84
121
|
|
|
85
122
|
function prettyFileSize(value) {
|