@jseeio/jsee 0.3.7 → 0.3.9

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.
@@ -0,0 +1,350 @@
1
+ <template>
2
+ <div :id="id" class="vfp">
3
+ <!-- Based on https://github.com/rowanwins/vue-file-picker/ -->
4
+ <div
5
+ class="vfp-bgArea"
6
+ :class="{ 'vfp-active': isActive }"
7
+ @dragover="setActive"
8
+ @dragleave="cancelActive"
9
+ @drop="fileAdded"
10
+ >
11
+ <!-- icon -->
12
+ <div class="vfp-iconHolder vfp-gridItem"
13
+ v-if="!showUrlInputAuto"
14
+ >
15
+ <slot name="icon">
16
+ <svg height="40" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
17
+ <path
18
+ d="M18 32h12v-12h8l-14-14-14 14h8zm-8 4h28v4h-28z"
19
+ fill="#CACFD2"
20
+ />
21
+ </svg>
22
+ </slot>
23
+ </div>
24
+
25
+ <!-- invisible native file input -->
26
+ <input
27
+ id="vfp-filePicker"
28
+ class="vfp-inputfile vfp-gridItem"
29
+ type="file"
30
+ name="vfp-filePicker"
31
+ :accept="accept"
32
+ :multiple="allowMultiple"
33
+ @change="fileAdded"
34
+ />
35
+
36
+ <!-- main label OR url input (switches) -->
37
+ <label
38
+ v-if="!showUrlInputAuto"
39
+ class="vfp-label vfp-gridItem vfp-clickable"
40
+ for="vfp-filePicker"
41
+ >
42
+ <slot name="label">
43
+ <strong>{{ label }}</strong>
44
+ </slot>
45
+ <span class="vfp-info">{{ info }}</span>
46
+ </label>
47
+
48
+ <!-- secondary toggle -->
49
+ <div
50
+ v-if="!showUrlInputAuto"
51
+ >
52
+ <!-- Trigger File input -->
53
+ <button
54
+ class="vfp-button vfp-gridItem"
55
+ @click="activateFileDialog"
56
+ style="margin-right:5px"
57
+ >
58
+ From Disk
59
+ </button>
60
+
61
+ <button
62
+ v-if="!showUrlInputAuto"
63
+ class="vfp-button vfp-gridItem"
64
+ @click="activateUrl"
65
+ >
66
+ From URL
67
+ </button>
68
+ </div>
69
+
70
+ <!-- v-model.trim="urlInput" -->
71
+ <div v-if="showUrlInputAuto">
72
+ <input
73
+ class="vfp-urlInput vfp-gridItem"
74
+ type="text"
75
+ v-model.trim="urlModel"
76
+ placeholder="Paste file URL here"
77
+ :style="{
78
+ borderColor: urlSuccess ? 'green' : urlError ? 'red' : ''
79
+ }"
80
+ />
81
+ <button
82
+ class="vfp-urlInput vfp-gridItem vfp-button"
83
+ @click="loadUrl"
84
+ >
85
+ Load
86
+ </button>
87
+ <button
88
+ class="vfp-urlInput vfp-gridItem vfp-button"
89
+ @click="clearUrl"
90
+ >
91
+ Cancel
92
+ </button>
93
+ </div>
94
+
95
+
96
+ </div>
97
+ </div>
98
+ </template>
99
+
100
+ <script>
101
+ export default {
102
+ name: 'FilePicker',
103
+ props: {
104
+ id: { type: String, default: 'filePicker' },
105
+ accept: { type: String, default: '*/*' },
106
+ allowMultiple: { type: Boolean, default: false },
107
+ modelValue: { type: [String, Object], default: '' },
108
+ url: { type: String, default: '' },
109
+ raw: { type: Boolean, default: false },
110
+ autoload: { type: Boolean, default: false },
111
+ labelValue: { type: String, default: 'Choose File' }
112
+ },
113
+ emits : ['update:modelValue', 'update:url', 'change'],
114
+ data () {
115
+ return {
116
+ isActive: false,
117
+ urlInput: '',
118
+ urlSuccess: false,
119
+ urlError: false,
120
+ label: this.labelValue,
121
+ info: 'or drag and drop it here',
122
+ showUrlInput: true,
123
+ urlAutoLoadHandled: false
124
+ }
125
+ },
126
+ computed: {
127
+ showUrlInputAuto () {
128
+ return this.url.length > 0 && this.showUrlInput
129
+ },
130
+ requiresTypeCheck () { return this.accept !== '*/*' },
131
+ acceptedTypes () { return this.accept.split(',') },
132
+ urlModel: {
133
+ get () { return this.url },
134
+ set (v) { this.$emit('update:url', v) }
135
+ }
136
+ },
137
+ watch: {
138
+ autoload (value) {
139
+ if (!value) {
140
+ this.urlAutoLoadHandled = false
141
+ return
142
+ }
143
+ this.tryAutoLoadUrl()
144
+ },
145
+ url (value) {
146
+ if (!value) {
147
+ this.urlAutoLoadHandled = false
148
+ return
149
+ }
150
+ this.tryAutoLoadUrl()
151
+ }
152
+ },
153
+ mounted () {
154
+ this.tryAutoLoadUrl()
155
+ },
156
+ methods: {
157
+ /* drag-drop handling */
158
+ cancelHandlers (e) { e.preventDefault(); e.stopPropagation() },
159
+ setActive (e) { this.isActive = true; this.cancelHandlers(e) },
160
+ cancelActive (e) { this.isActive = false; this.cancelHandlers(e) },
161
+
162
+ fileAdded (e) {
163
+ this.isActive = false;
164
+ this.cancelHandlers(e);
165
+
166
+ const wasDropped = !!e.dataTransfer;
167
+ if (wasDropped && this.urlModel && this.urlModel.length > 0) {
168
+ console.log('[File picker] URL mode active, ignoring dropped files');
169
+ return;
170
+ }
171
+ const files = wasDropped ? e.dataTransfer.files : e.target.files;
172
+ this.label = Array.from(files).map(f => f.name).join(', ');
173
+ const totalSizeinKB = Array.from(files).reduce((acc, f) => acc + f.size / 1024, 0).toFixed(2)
174
+ this.info = `Selected ${files.length} file(s) of size ${totalSizeinKB} KB`;
175
+
176
+ if (wasDropped && !this.allowMultiple && files.length > 1)
177
+ throw new Error('vue-file-picker: Multiple files are not allowed');
178
+ if (wasDropped && this.requiresTypeCheck)
179
+ for (const f of files)
180
+ if (!this.acceptedTypes.includes(f.type))
181
+ throw new Error('vue-file-picker: File type not allowed');
182
+
183
+ console.log('[File picker] Files added:', files);
184
+ this.loadFile(files);
185
+ },
186
+
187
+ loadFile (e) {
188
+ const files = e.target ? e.target.files : e
189
+ if (this.raw) {
190
+ const fileValue = this.allowMultiple ? Array.from(files) : files[0]
191
+ this.$emit('update:modelValue', fileValue)
192
+ this.$emit('change')
193
+ return
194
+ }
195
+ const reader = new FileReader()
196
+ reader.readAsText(files[0])
197
+ reader.onload = () => {
198
+ // No need to check for reactivity here, as the parent component will handle it
199
+ // Just trigger basic change event
200
+ this.$emit('update:modelValue', reader.result)
201
+ this.$emit('change')
202
+ }
203
+ },
204
+
205
+ loadUrl () {
206
+ if (this.urlModel && this.urlModel.length > 0) {
207
+ if (this.raw) {
208
+ this.urlSuccess = true
209
+ this.urlError = false
210
+ this.label = this.urlModel.split('/').pop().split('?')[0] || 'File from URL'
211
+ this.rawUrl = this.urlModel.split('/').slice(0, -1).join('/') // Extract URL path
212
+ this.showUrlInput = false // Hide URL input after successful load
213
+ this.info = `Loaded URL handle: ${this.rawUrl || this.urlModel}`
214
+ this.$emit('update:modelValue', { kind: 'url', url: this.urlModel })
215
+ this.$emit('change')
216
+ return
217
+ }
218
+
219
+ fetch(this.urlModel)
220
+ .then(response => response.text())
221
+ .then(text => {
222
+ this.urlSuccess = true
223
+ this.urlError = false
224
+ this.label = this.urlModel.split('/').pop().split('?')[0] || 'File from URL'
225
+ this.rawUrl = this.urlModel.split('/').slice(0, -1).join('/') // Extract URL path
226
+ this.showUrlInput = false // Hide URL input after successful load
227
+ // Show file size
228
+ this.info = `Loaded from URL: ${this.rawUrl} (${(text.length / 1024).toFixed(2)} KB)`
229
+ this.$emit('update:modelValue', text)
230
+ this.$emit('change')
231
+ })
232
+ .catch(error => {
233
+ this.urlSuccess = false
234
+ this.urlError = true
235
+ console.error('Error fetching URL:', error)
236
+ })
237
+ }
238
+ },
239
+
240
+ tryAutoLoadUrl () {
241
+ if (!this.autoload || this.urlAutoLoadHandled) {
242
+ return
243
+ }
244
+ if (!(this.urlModel && this.urlModel.length > 0)) {
245
+ return
246
+ }
247
+ this.urlAutoLoadHandled = true
248
+ this.loadUrl()
249
+ },
250
+
251
+ /* switch to URL mode */
252
+ activateUrl () {
253
+ this.urlModel = this.urlModel || 'http://'
254
+ this.showUrlInput = true
255
+ this.$nextTick(() => this.$el.querySelector('.vfp-urlInput').focus())
256
+ },
257
+
258
+ activateFileDialog () {
259
+ this.$el.querySelector('#vfp-filePicker').click()
260
+ },
261
+
262
+ clearUrl () {
263
+ this.showUrlInput = false
264
+ // this.urlModel = ''
265
+ // this.urlSuccess = false
266
+ // this.urlError = false
267
+ // this.$emit('update:url', '')
268
+ // this.$emit('update:modelValue', '')
269
+ }
270
+ }
271
+ }
272
+ </script>
273
+
274
+ <style lang="scss">
275
+ .vfp {
276
+ display: flex;
277
+ min-height: 130px;
278
+ width: 100%;
279
+
280
+ .vfp-bgArea {
281
+ transition: 0.3s;
282
+ background: #F2F3F4;
283
+ display: grid;
284
+ grid-template-rows: 40% 32% 28%;
285
+ padding: 20px 10px;
286
+ width: 100%;
287
+ outline: 1px dashed #CACFD2;
288
+ outline-offset: -10px;
289
+ color: #3b3e40;
290
+ text-align: center;
291
+ }
292
+
293
+ .vfp-inputfile {
294
+ width: 0.1px;
295
+ height: 0.1px;
296
+ opacity: 0;
297
+ position: absolute;
298
+ }
299
+
300
+ .vfp-gridItem { align-self: center; justify-self: center; }
301
+
302
+ .vfp-label { cursor: pointer; font-size: 0.9rem; font-family: monospace; display:block; }
303
+ .vfp-info {
304
+ font-size: 9px;
305
+ color: #7f8c8d;
306
+ margin-top: 1px;
307
+ margin-bottom: 5px;
308
+ display: block;
309
+ }
310
+ .vfp-urlToggle { cursor: pointer; font-size: 0.8rem; }
311
+ .vfp-urlInput {
312
+ margin-top: 10px;
313
+ width: calc(100% - 20px);
314
+ padding: 6px 6px !important;
315
+ border: 1px solid #CACFD2;
316
+ border-radius: 4px;
317
+ font-size: 0.9rem;
318
+ }
319
+
320
+ .vfp-button {
321
+ cursor: pointer;
322
+ border: none;
323
+ padding: 4px 10px;
324
+ border-radius: 4px;
325
+
326
+ // hover effect
327
+ background-color: #E4E7EA;
328
+ &:hover {
329
+ background-color: #D7DBDD;
330
+ }
331
+ }
332
+
333
+ button.vfp-urlInput {
334
+ margin-top: 2px;
335
+ }
336
+
337
+ .vfp-active {
338
+ background-color: #D7DBDD;
339
+ outline-color: #F2F3F4;
340
+ }
341
+
342
+ @media only screen and (max-width: 440px) {
343
+ .vfp-bgArea {
344
+ padding: 18px 10px;
345
+ grid-template-rows: 45% 25% 30%;
346
+ grid-row-gap: 5px;
347
+ }
348
+ }
349
+ }
350
+ </style>
@@ -0,0 +1,15 @@
1
+ (function (root) {
2
+ function kebabCase (input) {
3
+ if (typeof input !== 'string') {
4
+ return ''
5
+ }
6
+ return input
7
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
8
+ .replace(/[^a-zA-Z0-9]+/g, '-')
9
+ .replace(/^-+|-+$/g, '')
10
+ .toLowerCase()
11
+ }
12
+
13
+ root._ = root._ || {}
14
+ root._.kebabCase = kebabCase
15
+ })(typeof self !== 'undefined' ? self : window)
@@ -0,0 +1,3 @@
1
+ name,age
2
+ Alice,30
3
+ Bob,25