pimelon-ui 0.0.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 (39) hide show
  1. package/license.md +0 -0
  2. package/package.json +33 -0
  3. package/readme.md +0 -0
  4. package/src/components/Alert.vue +57 -0
  5. package/src/components/Avatar.vue +61 -0
  6. package/src/components/Badge.vue +40 -0
  7. package/src/components/Button.vue +134 -0
  8. package/src/components/Card.vue +37 -0
  9. package/src/components/Dialog.vue +195 -0
  10. package/src/components/Dropdown.vue +113 -0
  11. package/src/components/ErrorMessage.vue +15 -0
  12. package/src/components/FeatherIcon.vue +55 -0
  13. package/src/components/FileUploader.vue +220 -0
  14. package/src/components/GreenCheckIcon.vue +16 -0
  15. package/src/components/Input.vue +169 -0
  16. package/src/components/Link.vue +28 -0
  17. package/src/components/ListItem.vue +28 -0
  18. package/src/components/LoadingIndicator.vue +12 -0
  19. package/src/components/LoadingText.vue +21 -0
  20. package/src/components/Modal.vue +67 -0
  21. package/src/components/Popover.vue +192 -0
  22. package/src/components/Resource.vue +21 -0
  23. package/src/components/Spinner.vue +27 -0
  24. package/src/components/SuccessMessage.vue +15 -0
  25. package/src/components/TextEditor/Menu.vue +32 -0
  26. package/src/components/TextEditor/TextEditor.vue +193 -0
  27. package/src/components/TextEditor/commands.js +79 -0
  28. package/src/components/TextEditor/index.js +1 -0
  29. package/src/components/Toast.vue +167 -0
  30. package/src/directives/onOutsideClick.js +28 -0
  31. package/src/index.js +33 -0
  32. package/src/style.css +15 -0
  33. package/src/utils/call.js +98 -0
  34. package/src/utils/debounce.js +15 -0
  35. package/src/utils/plugin.js +24 -0
  36. package/src/utils/resources.js +510 -0
  37. package/src/utils/socketio.js +9 -0
  38. package/src/utils/tailwind.config.js +110 -0
  39. package/src/utils/vite-dev-server.js +14 -0
@@ -0,0 +1,510 @@
1
+ import { call, debounce } from 'pimelon-ui'
2
+ import { reactive, watch } from 'vue'
3
+
4
+ let cached = {}
5
+ let documentCache = reactive({})
6
+ let listCache = reactive({})
7
+
8
+ export function createResource(options, vm, getResource) {
9
+ let cacheKey = null
10
+ if (options.cache) {
11
+ cacheKey = getCacheKey(options.cache)
12
+ if (cached[cacheKey]) {
13
+ return cached[cacheKey]
14
+ }
15
+ }
16
+
17
+ if (typeof options == 'string') {
18
+ options = {
19
+ method: options,
20
+ auto: true,
21
+ }
22
+ }
23
+
24
+ let resourceFetcher = getResource || call
25
+ let fetchFunction = options.debounce
26
+ ? debounce(fetch, options.debounce)
27
+ : fetch
28
+
29
+ let out = reactive({
30
+ data: options.initialData || null,
31
+ previousData: null,
32
+ loading: false,
33
+ fetched: false,
34
+ error: null,
35
+ auto: options.auto,
36
+ params: null,
37
+ fetch: fetchFunction,
38
+ reload: fetchFunction,
39
+ submit: fetchFunction,
40
+ reset,
41
+ update,
42
+ setData,
43
+ })
44
+
45
+ async function fetch(params, tempOptions = {}) {
46
+ if (params instanceof Event) {
47
+ params = null
48
+ }
49
+ params = params || out.params
50
+ if (options.makeParams) {
51
+ params = options.makeParams.call(vm, params)
52
+ }
53
+ out.params = params
54
+ out.previousData = out.data ? JSON.parse(JSON.stringify(out.data)) : null
55
+ out.loading = true
56
+
57
+ if (options.onFetch) {
58
+ options.onFetch.call(vm, out.params)
59
+ }
60
+
61
+ let validateFunction = tempOptions.validate || options.validate
62
+ let errorFunctions = [options.onError, tempOptions.onError]
63
+ let successFunctions = [options.onSuccess, tempOptions.onSuccess]
64
+
65
+ if (validateFunction) {
66
+ let invalidMessage
67
+ try {
68
+ invalidMessage = await validateFunction.call(vm, out.params)
69
+ if (invalidMessage && typeof invalidMessage == 'string') {
70
+ let error = new Error(invalidMessage)
71
+ handleError(error, errorFunctions)
72
+ out.loading = false
73
+ return
74
+ }
75
+ } catch (error) {
76
+ handleError(error, errorFunctions)
77
+ out.loading = false
78
+ return
79
+ }
80
+ }
81
+
82
+ try {
83
+ let data = await resourceFetcher(options.method, params || options.params)
84
+ out.data = data
85
+ out.fetched = true
86
+ for (let fn of successFunctions) {
87
+ if (fn) {
88
+ fn.call(vm, data)
89
+ }
90
+ }
91
+ } catch (error) {
92
+ handleError(error, errorFunctions)
93
+ }
94
+ out.loading = false
95
+ }
96
+
97
+ function update({ method, params, auto }) {
98
+ if (method && method !== options.method) {
99
+ out.method = method
100
+ }
101
+ if (params && params !== options.params) {
102
+ out.params = params
103
+ }
104
+ if (auto !== undefined && auto !== out.auto) {
105
+ out.auto = auto
106
+ }
107
+ }
108
+
109
+ function reset() {
110
+ out.data = options.initialData || null
111
+ out.previousData = null
112
+ out.loading = false
113
+ out.fetched = false
114
+ out.error = null
115
+ out.params = null
116
+ out.auto = options.auto
117
+ }
118
+
119
+ function handleError(error, errorFunctions) {
120
+ console.error(error)
121
+ if (out.previousData) {
122
+ out.data = out.previousData
123
+ }
124
+ out.error = error
125
+ for (let fn of errorFunctions) {
126
+ if (fn) {
127
+ fn.call(vm, error)
128
+ }
129
+ }
130
+ }
131
+
132
+ // usage:
133
+ // setData(newData) or
134
+ // setData(data => data.filter(d => !d.deleted))
135
+ function setData(data) {
136
+ if (typeof data === 'function') {
137
+ data = data.call(vm, out.data)
138
+ }
139
+ out.data = data
140
+ }
141
+
142
+ if (cacheKey && !cached[cacheKey]) {
143
+ cached[cacheKey] = out
144
+ }
145
+
146
+ return out
147
+ }
148
+
149
+ export function createDocumentResource(options, vm) {
150
+ if (!(options.doctype && options.name)) return
151
+
152
+ let cacheKey = getCacheKey([options.doctype, options.name])
153
+ if (documentCache[cacheKey]) {
154
+ return documentCache[cacheKey]
155
+ }
156
+
157
+ let setValueOptions = {
158
+ method: 'melon.client.set_value',
159
+ makeParams(values) {
160
+ return {
161
+ doctype: out.doctype,
162
+ name: out.name,
163
+ fieldname: values,
164
+ }
165
+ },
166
+ onSuccess(data) {
167
+ out.doc = transform(data)
168
+ },
169
+ }
170
+
171
+ let out = reactive({
172
+ doctype: options.doctype,
173
+ name: options.name,
174
+ doc: null,
175
+ get: createResource(
176
+ {
177
+ method: 'melon.client.get',
178
+ makeParams() {
179
+ return {
180
+ doctype: out.doctype,
181
+ name: out.name,
182
+ }
183
+ },
184
+ onSuccess(data) {
185
+ out.doc = transform(data)
186
+ },
187
+ },
188
+ vm
189
+ ),
190
+ setValue: createResource(setValueOptions, vm),
191
+ setValueDebounced: createResource(
192
+ {
193
+ ...setValueOptions,
194
+ debounce: options.debounce || 500,
195
+ },
196
+ vm
197
+ ),
198
+ delete: createResource(
199
+ {
200
+ method: 'melon.client.delete',
201
+ makeParams() {
202
+ return {
203
+ doctype: out.doctype,
204
+ name: out.name,
205
+ }
206
+ },
207
+ onSuccess() {
208
+ out.doc = null
209
+ },
210
+ },
211
+ vm
212
+ ),
213
+ update,
214
+ reload,
215
+ })
216
+
217
+ for (let method in options.whitelistedMethods) {
218
+ let methodName = options.whitelistedMethods[method]
219
+ out[method] = createResource(
220
+ {
221
+ method: 'run_doc_method',
222
+ makeParams(values) {
223
+ return {
224
+ dt: out.doctype,
225
+ dn: out.name,
226
+ method: methodName,
227
+ args: JSON.stringify(values),
228
+ }
229
+ },
230
+ onSuccess(data) {
231
+ if (data.docs) {
232
+ for (let doc of data.docs) {
233
+ if (doc.doctype === out.doctype && doc.name === out.name) {
234
+ out.doc = transform(doc)
235
+ break
236
+ }
237
+ }
238
+ }
239
+ },
240
+ },
241
+ vm
242
+ )
243
+ }
244
+
245
+ function update(updatedOptions) {
246
+ out.doctype = updatedOptions.doctype
247
+ out.name = updatedOptions.name
248
+ out.get.fetch()
249
+ }
250
+
251
+ function reload() {
252
+ return out.get.fetch()
253
+ }
254
+
255
+ function transform(doc) {
256
+ if (options.transform) {
257
+ let returnValue = options.transform.call(vm, doc)
258
+ if (typeof returnValue === 'object') {
259
+ return returnValue
260
+ }
261
+ }
262
+ return doc
263
+ }
264
+
265
+ // fetch the doc
266
+ out.get.fetch()
267
+ // cache
268
+ documentCache[cacheKey] = out
269
+ return out
270
+ }
271
+
272
+ function createListResource(options, vm, getResource) {
273
+ if (!options.doctype) return
274
+
275
+ let cacheKey = getCacheKey(options.cache)
276
+ if (listCache[cacheKey]) {
277
+ return listCache[cacheKey]
278
+ }
279
+
280
+ let out = reactive({
281
+ doctype: options.doctype,
282
+ fields: options.fields,
283
+ filters: options.filters,
284
+ order_by: options.order_by,
285
+ start: options.start,
286
+ limit: options.limit,
287
+ data: null,
288
+ list: createResource(
289
+ {
290
+ method: 'melon.client.get_list',
291
+ makeParams() {
292
+ return {
293
+ doctype: out.doctype,
294
+ fields: out.fields,
295
+ filters: out.filters,
296
+ order_by: out.order_by,
297
+ limit_start: out.start,
298
+ limit_page_length: out.limit,
299
+ }
300
+ },
301
+ onSuccess(data) {
302
+ out.data = transform(data)
303
+ options.onSuccess && options.onSuccess.call(vm, out.data)
304
+ },
305
+ },
306
+ vm
307
+ ),
308
+ insert: createResource(
309
+ {
310
+ method: 'melon.client.insert',
311
+ makeParams(values) {
312
+ return {
313
+ doc: {
314
+ doctype: out.doctype,
315
+ ...values,
316
+ },
317
+ }
318
+ },
319
+ onSuccess() {
320
+ out.list.fetch()
321
+ },
322
+ },
323
+ vm
324
+ ),
325
+ setValue: createResource(
326
+ {
327
+ method: 'melon.client.set_value',
328
+ makeParams(options) {
329
+ let { name, ...values } = options
330
+ return {
331
+ doctype: out.doctype,
332
+ name: name,
333
+ fieldname: values,
334
+ }
335
+ },
336
+ onSuccess() {
337
+ out.list.fetch()
338
+ },
339
+ },
340
+ vm
341
+ ),
342
+ delete: createResource(
343
+ {
344
+ method: 'melon.client.delete',
345
+ makeParams(name) {
346
+ return {
347
+ doctype: out.doctype,
348
+ name,
349
+ }
350
+ },
351
+ onSuccess() {
352
+ out.list.fetch()
353
+ },
354
+ },
355
+ vm
356
+ ),
357
+ update,
358
+ reload,
359
+ setData,
360
+ })
361
+
362
+ function update(updatedOptions) {
363
+ out.doctype = updatedOptions.doctype
364
+ out.fields = updatedOptions.fields
365
+ out.filters = updatedOptions.filters
366
+ out.order_by = updatedOptions.order_by
367
+ out.start = updatedOptions.start
368
+ out.limit = updatedOptions.limit
369
+ out.list.fetch()
370
+ }
371
+
372
+ function transform(data) {
373
+ if (options.transform) {
374
+ let returnValue = options.transform.call(vm, data)
375
+ if (typeof returnValue != null) {
376
+ return returnValue
377
+ }
378
+ }
379
+ return data
380
+ }
381
+
382
+ function reload() {
383
+ return out.list.fetch()
384
+ }
385
+
386
+ function setData(data) {
387
+ if (typeof data === 'function') {
388
+ data = data.call(vm, out.data)
389
+ }
390
+ out.data = data
391
+ }
392
+
393
+ // fetch list
394
+ out.list.fetch()
395
+
396
+ // cache
397
+ listCache[cacheKey] = out
398
+
399
+ return out
400
+ }
401
+
402
+ function createResourceForOptions(options, vm, getResource) {
403
+ if (options.type === 'document') {
404
+ return createDocumentResource(options, vm, getResource)
405
+ }
406
+ if (options.type === 'list') {
407
+ return createListResource(options, vm, getResource)
408
+ }
409
+ return createResource(options, vm, getResource)
410
+ }
411
+
412
+ function getCacheKey(cacheKey) {
413
+ if (typeof cacheKey === 'string') {
414
+ cacheKey = [cacheKey]
415
+ }
416
+ return JSON.stringify(cacheKey)
417
+ }
418
+
419
+ let createMixin = (mixinOptions) => ({
420
+ created() {
421
+ if (this.$options.resources) {
422
+ this._resources = reactive({})
423
+ for (let key in this.$options.resources) {
424
+ let options = this.$options.resources[key]
425
+
426
+ if (typeof options == 'function') {
427
+ watch(
428
+ () => {
429
+ try {
430
+ return options.call(this)
431
+ } catch (error) {
432
+ console.warn('Failed to get resource options\n\n', error)
433
+ return null
434
+ }
435
+ },
436
+ (updatedOptions, oldVal) => {
437
+ if (!updatedOptions) {
438
+ return
439
+ }
440
+
441
+ let changed =
442
+ !oldVal ||
443
+ JSON.stringify(updatedOptions) !== JSON.stringify(oldVal)
444
+
445
+ if (!changed) return
446
+
447
+ let resource = this._resources[key]
448
+ if (!resource) {
449
+ resource = createResourceForOptions(
450
+ updatedOptions,
451
+ this,
452
+ mixinOptions.getResource
453
+ )
454
+ this._resources[key] = resource
455
+ } else {
456
+ resource.update(updatedOptions)
457
+ }
458
+ if (resource && resource.auto) {
459
+ resource.fetch()
460
+ }
461
+ },
462
+ {
463
+ immediate: true,
464
+ }
465
+ )
466
+ } else {
467
+ let resource = createResourceForOptions(
468
+ options,
469
+ this,
470
+ mixinOptions.getResource
471
+ )
472
+ this._resources[key] = resource
473
+ if (resource.auto) {
474
+ resource.fetch()
475
+ }
476
+ }
477
+ }
478
+ }
479
+ },
480
+ methods: {
481
+ $getResource(cache) {
482
+ let cacheKey = getCacheKey(cache)
483
+ return cached[cacheKey] || null
484
+ },
485
+ $getDocumentResource(doctype, name) {
486
+ let cacheKey = getCacheKey([doctype, name])
487
+ return documentCache[cacheKey] || null
488
+ },
489
+ $getListResource(cache) {
490
+ let cacheKey = getCacheKey(cache)
491
+ return listCache[cacheKey] || null
492
+ },
493
+ $refetchResource(cache) {
494
+ let resource = this.$getResource(cache)
495
+ resource && resource.fetch()
496
+ },
497
+ },
498
+ computed: {
499
+ $resources() {
500
+ return this._resources
501
+ },
502
+ },
503
+ })
504
+
505
+ export default {
506
+ install(app, options) {
507
+ let resourceMixin = createMixin(options)
508
+ app.mixin(resourceMixin)
509
+ },
510
+ }
@@ -0,0 +1,9 @@
1
+ import io from 'socket.io-client/dist/socket.io.slim'
2
+
3
+ let host = window.location.hostname
4
+ let port = window.location.port ? ':9000' : ''
5
+ let protocol = port ? 'http' : 'https'
6
+ let url = `${protocol}://${host}${port}`
7
+ let socket = io(url)
8
+
9
+ export default socket
@@ -0,0 +1,110 @@
1
+ module.exports = {
2
+ theme: {
3
+ extend: {
4
+ fontFamily: {
5
+ sans: ['Inter', 'sans-serif'],
6
+ },
7
+ fontSize: {
8
+ xs: '11px',
9
+ sm: '12px',
10
+ base: '13px',
11
+ lg: '14px',
12
+ xl: '16px',
13
+ '2xl': '18px',
14
+ '3xl': '20px',
15
+ '4xl': '22px',
16
+ '5xl': '24px',
17
+ '6xl': '26px',
18
+ '7xl': '28px',
19
+ },
20
+ width: {
21
+ 112: '28rem',
22
+ wizard: '650px',
23
+ },
24
+ minWidth: {
25
+ 40: '10rem',
26
+ },
27
+ maxHeight: {
28
+ 52: '13rem',
29
+ },
30
+ borderColor: (theme) => ({
31
+ DEFAULT: theme('colors.gray.200'),
32
+ }),
33
+ colors: {
34
+ brand: '#2490EF',
35
+ 'brand-100': '#f4f9ff',
36
+ black: '#112B42',
37
+ blue: {
38
+ 50: '#F0F8FE',
39
+ 100: '#D3E9FC',
40
+ 200: '#A7D3F9',
41
+ 300: '#7CBCF5',
42
+ 400: '#50A6F2',
43
+ 500: '#2490EF',
44
+ 600: '#1579D0',
45
+ 700: '#1366AE',
46
+ 800: '#154875',
47
+ 900: '#1A4469',
48
+ },
49
+ gray: {
50
+ 50: '#F9FAFA',
51
+ 100: '#F4F5F6',
52
+ 200: '#EBEEF0',
53
+ 300: '#DCE0E3',
54
+ 400: '#C0C6CC',
55
+ 500: '#98A1A9',
56
+ 600: '#687178',
57
+ 700: '#505A62',
58
+ 800: '#333C44',
59
+ 900: '#1F272E',
60
+ },
61
+ purple: {
62
+ 900: '#44427B',
63
+ 800: '#5552BC',
64
+ 700: '#6461D6',
65
+ 600: '#807DDE',
66
+ 500: '#928EF5',
67
+ 400: '#B7B6FC',
68
+ 300: '#D6D5F6',
69
+ 200: '#E8E8F7',
70
+ 100: '#F2F2FD',
71
+ 50: '#F8F8FC',
72
+ },
73
+ },
74
+ },
75
+ container: {
76
+ padding: {
77
+ xl: '5rem',
78
+ },
79
+ },
80
+ screens: {
81
+ sm: '640px',
82
+ md: '768px',
83
+ lg: '1024px',
84
+ xl: '1280px',
85
+ },
86
+ },
87
+ plugins: [
88
+ require('@tailwindcss/forms'),
89
+ require('@tailwindcss/typography'),
90
+ require('tailwindcss/plugin')(function ({ addUtilities, theme }) {
91
+ addUtilities({
92
+ '.bg-gradient-blue': {
93
+ 'background-image': `linear-gradient(180deg,#2c9af1 0%, ${theme(
94
+ 'colors.blue.500'
95
+ )} 100%)`,
96
+ },
97
+ })
98
+ addUtilities(
99
+ {
100
+ '.bg-gradient-none': {
101
+ 'background-image': 'none',
102
+ },
103
+ },
104
+ {
105
+ variants: ['focus', 'hover'],
106
+ }
107
+ )
108
+ }),
109
+ ],
110
+ }
@@ -0,0 +1,14 @@
1
+ module.exports = {
2
+ getProxyOptions({ port }) {
3
+ return {
4
+ '^/(app|api|assets|files)': {
5
+ target: `http://localhost:${port}`,
6
+ ws: true,
7
+ router: function (req) {
8
+ const site_name = req.headers.host.split(':')[0]
9
+ return `http://${site_name}:${port}`
10
+ },
11
+ },
12
+ }
13
+ },
14
+ }