@windwalker-io/unicorn-next 0.1.0

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 (175) hide show
  1. package/.editorconfig +18 -0
  2. package/.gulp.json +7 -0
  3. package/bin/release.mjs +47 -0
  4. package/dist/chunks/_arrayPush.js +168 -0
  5. package/dist/chunks/_arrayPush.js.map +1 -0
  6. package/dist/chunks/_baseRest.js +73 -0
  7. package/dist/chunks/_baseRest.js.map +1 -0
  8. package/dist/chunks/_commonjsHelpers.js +7 -0
  9. package/dist/chunks/_commonjsHelpers.js.map +1 -0
  10. package/dist/chunks/_getPrototype.js +130 -0
  11. package/dist/chunks/_getPrototype.js.map +1 -0
  12. package/dist/chunks/button-radio.js +147 -0
  13. package/dist/chunks/button-radio.js.map +1 -0
  14. package/dist/chunks/checkboxes-multi-select.js +44 -0
  15. package/dist/chunks/checkboxes-multi-select.js.map +1 -0
  16. package/dist/chunks/cloneDeep.js +287 -0
  17. package/dist/chunks/cloneDeep.js.map +1 -0
  18. package/dist/chunks/cropper.min.js +5 -0
  19. package/dist/chunks/cropper.min.js.map +1 -0
  20. package/dist/chunks/field-cascade-select.js +256 -0
  21. package/dist/chunks/field-cascade-select.js.map +1 -0
  22. package/dist/chunks/field-file-drag.js +218 -0
  23. package/dist/chunks/field-file-drag.js.map +1 -0
  24. package/dist/chunks/field-flatpickr.js +893 -0
  25. package/dist/chunks/field-flatpickr.js.map +1 -0
  26. package/dist/chunks/field-modal-select.js +403 -0
  27. package/dist/chunks/field-modal-select.js.map +1 -0
  28. package/dist/chunks/field-modal-tree.js +790 -0
  29. package/dist/chunks/field-modal-tree.js.map +1 -0
  30. package/dist/chunks/field-multi-uploader.js +256 -0
  31. package/dist/chunks/field-multi-uploader.js.map +1 -0
  32. package/dist/chunks/field-repeatable.js +132 -0
  33. package/dist/chunks/field-repeatable.js.map +1 -0
  34. package/dist/chunks/field-single-image-drag.js +338 -0
  35. package/dist/chunks/field-single-image-drag.js.map +1 -0
  36. package/dist/chunks/form.js +154 -0
  37. package/dist/chunks/form.js.map +1 -0
  38. package/dist/chunks/grid.js +345 -0
  39. package/dist/chunks/grid.js.map +1 -0
  40. package/dist/chunks/http-client.js +229 -0
  41. package/dist/chunks/http-client.js.map +1 -0
  42. package/dist/chunks/iframe-modal.js +124 -0
  43. package/dist/chunks/iframe-modal.js.map +1 -0
  44. package/dist/chunks/index.js +309 -0
  45. package/dist/chunks/index.js.map +1 -0
  46. package/dist/chunks/isArguments.js +146 -0
  47. package/dist/chunks/isArguments.js.map +1 -0
  48. package/dist/chunks/keep-tab.js +101 -0
  49. package/dist/chunks/keep-tab.js.map +1 -0
  50. package/dist/chunks/legacy.js +210 -0
  51. package/dist/chunks/legacy.js.map +1 -0
  52. package/dist/chunks/list-dependent.js +231 -0
  53. package/dist/chunks/list-dependent.js.map +1 -0
  54. package/dist/chunks/s3-multipart-uploader.js +172 -0
  55. package/dist/chunks/s3-multipart-uploader.js.map +1 -0
  56. package/dist/chunks/s3-uploader.js +136 -0
  57. package/dist/chunks/s3-uploader.js.map +1 -0
  58. package/dist/chunks/show-on.js +237 -0
  59. package/dist/chunks/show-on.js.map +1 -0
  60. package/dist/chunks/tinymce.js +196 -0
  61. package/dist/chunks/tinymce.js.map +1 -0
  62. package/dist/chunks/ui-bootstrap5.js +71 -0
  63. package/dist/chunks/ui-bootstrap5.js.map +1 -0
  64. package/dist/chunks/unicorn.js +2202 -0
  65. package/dist/chunks/unicorn.js.map +1 -0
  66. package/dist/chunks/validation.js +854 -0
  67. package/dist/chunks/validation.js.map +1 -0
  68. package/dist/editor.css +1 -0
  69. package/dist/index.d.ts +1427 -0
  70. package/dist/multi-level-menu.css +1 -0
  71. package/dist/switcher.css +1 -0
  72. package/dist/unicorn-next.css +12 -0
  73. package/dist/unicorn.js +125 -0
  74. package/dist/unicorn.js.map +1 -0
  75. package/fusionfile.mjs +155 -0
  76. package/images/ajax-loader.gif +0 -0
  77. package/images/placeholder/avatar.png +0 -0
  78. package/images/placeholder/image-16x10.png +0 -0
  79. package/images/placeholder/image-16x9.png +0 -0
  80. package/images/placeholder/image-1x1.png +0 -0
  81. package/images/placeholder/image-4x3.png +0 -0
  82. package/package.json +102 -0
  83. package/scss/bootstrap/multi-level-menu.scss +121 -0
  84. package/scss/editor.scss +116 -0
  85. package/scss/field/file-drag.scss +102 -0
  86. package/scss/field/single-image-drag.scss +88 -0
  87. package/scss/field/vue-drag-uploader.scss +160 -0
  88. package/scss/switcher.scss +156 -0
  89. package/src/app.ts +128 -0
  90. package/src/bootstrap/button-radio.ts +208 -0
  91. package/src/bootstrap/keep-tab.ts +155 -0
  92. package/src/composable/index.ts +21 -0
  93. package/src/composable/useCheckboxesMultiSelect.ts +22 -0
  94. package/src/composable/useFieldCascadeSelect.ts +9 -0
  95. package/src/composable/useFieldFileDrag.ts +9 -0
  96. package/src/composable/useFieldFlatpickr.ts +3 -0
  97. package/src/composable/useFieldModalSelect.ts +6 -0
  98. package/src/composable/useFieldModalTree.ts +3 -0
  99. package/src/composable/useFieldMultiUploader.ts +3 -0
  100. package/src/composable/useFieldRepeatable.ts +9 -0
  101. package/src/composable/useFieldSingleImageDrag.ts +5 -0
  102. package/src/composable/useForm.ts +43 -0
  103. package/src/composable/useGrid.ts +57 -0
  104. package/src/composable/useHttp.ts +8 -0
  105. package/src/composable/useIframeModal.ts +9 -0
  106. package/src/composable/useListDependent.ts +26 -0
  107. package/src/composable/useQueue.ts +13 -0
  108. package/src/composable/useS3Uploader.ts +32 -0
  109. package/src/composable/useShowOn.ts +9 -0
  110. package/src/composable/useStack.ts +13 -0
  111. package/src/composable/useTinymce.ts +29 -0
  112. package/src/composable/useTomSelect.ts +72 -0
  113. package/src/composable/useUIBootstrap5.ts +48 -0
  114. package/src/composable/useUniDirective.ts +32 -0
  115. package/src/composable/useValidation.ts +39 -0
  116. package/src/data.ts +36 -0
  117. package/src/events.ts +73 -0
  118. package/src/legacy/legacy.ts +186 -0
  119. package/src/legacy/loader.ts +125 -0
  120. package/src/module/checkboxes-multi-select.ts +54 -0
  121. package/src/module/field-cascade-select.ts +292 -0
  122. package/src/module/field-file-drag.ts +292 -0
  123. package/src/module/field-flatpickr.ts +127 -0
  124. package/src/module/field-modal-select.ts +174 -0
  125. package/src/module/field-modal-tree.ts +27 -0
  126. package/src/module/field-multi-uploader.ts +361 -0
  127. package/src/module/field-repeatable.ts +202 -0
  128. package/src/module/field-single-image-drag.ts +468 -0
  129. package/src/module/form.ts +223 -0
  130. package/src/module/grid.ts +465 -0
  131. package/src/module/http-client.ts +243 -0
  132. package/src/module/iframe-modal.ts +167 -0
  133. package/src/module/list-dependent.ts +321 -0
  134. package/src/module/s3-multipart-uploader.ts +300 -0
  135. package/src/module/s3-uploader.ts +234 -0
  136. package/src/module/show-on.ts +173 -0
  137. package/src/module/tinymce.ts +263 -0
  138. package/src/module/ui-bootstrap5.ts +107 -0
  139. package/src/module/validation.ts +1019 -0
  140. package/src/plugin/index.ts +1 -0
  141. package/src/plugin/php-adapter.ts +65 -0
  142. package/src/polyfill/form-request-submit.ts +31 -0
  143. package/src/polyfill/index.ts +9 -0
  144. package/src/service/animate.ts +58 -0
  145. package/src/service/crypto.ts +27 -0
  146. package/src/service/dom-watcher.ts +62 -0
  147. package/src/service/dom.ts +265 -0
  148. package/src/service/helper.ts +48 -0
  149. package/src/service/index.ts +10 -0
  150. package/src/service/lang.ts +122 -0
  151. package/src/service/loader.ts +152 -0
  152. package/src/service/router.ts +118 -0
  153. package/src/service/ui.ts +497 -0
  154. package/src/service/uri.ts +106 -0
  155. package/src/types/base.ts +9 -0
  156. package/src/types/index.ts +4 -0
  157. package/src/types/modal-tree.ts +12 -0
  158. package/src/types/plugin.ts +6 -0
  159. package/src/types/shims.d.ts +18 -0
  160. package/src/types/ui.ts +6 -0
  161. package/src/unicorn.ts +63 -0
  162. package/src/utilities/arr.ts +25 -0
  163. package/src/utilities/base.ts +9 -0
  164. package/src/utilities/data.ts +48 -0
  165. package/src/utilities/index.ts +5 -0
  166. package/src/utilities/tree.ts +20 -0
  167. package/src/vue/components/ModalTree/ModalTreeApp.vue +175 -0
  168. package/src/vue/components/ModalTree/TreeItem.vue +262 -0
  169. package/src/vue/components/ModalTree/TreeModal.vue +225 -0
  170. package/tests/test.js +4 -0
  171. package/tsconfig.js.json +25 -0
  172. package/tsconfig.json +17 -0
  173. package/vite.assets.config.ts +61 -0
  174. package/vite.config.test.ts +36 -0
  175. package/vite.config.ts +112 -0
@@ -0,0 +1,127 @@
1
+ import { useCssImport, useImport, injectCssToDocument } from '../service';
2
+ import flatpickr from 'flatpickr';
3
+ import css from 'flatpickr/dist/flatpickr.css?inline';
4
+
5
+ injectCssToDocument(css);
6
+
7
+ class FlatpickrElement extends HTMLElement {
8
+ static get is() {
9
+ return 'uni-flatpickr';
10
+ }
11
+
12
+ instance!: flatpickr.Instance;
13
+
14
+ constructor() {
15
+ super();
16
+ }
17
+
18
+ get selector() {
19
+ return this.getAttribute('selector') || 'input';
20
+ }
21
+
22
+ get locale() {
23
+ return this.getAttribute('locale') || '';
24
+ }
25
+
26
+ // todo: Currently not support single option attributes
27
+ getOptions() {
28
+ const options: any = {};
29
+ const ignore = [
30
+ 'selector'
31
+ ];
32
+
33
+ this.getAttributeNames().forEach((name) => {
34
+ if (ignore.indexOf(name) !== -1) {
35
+ return;
36
+ }
37
+
38
+ options[name] = this.getAttribute(name);
39
+ });
40
+
41
+ return options;
42
+ }
43
+
44
+ get $input(): HTMLInputElement {
45
+ return this.querySelector<HTMLInputElement>('input')!;
46
+ }
47
+
48
+ async connectedCallback() {
49
+ let options: flatpickr.Options.Options = JSON.parse(this.getAttribute('options') || '{}') || {};
50
+
51
+ options.autoFillDefaultTime = true;
52
+ const now = new Date();
53
+ options.defaultHour = now.getHours();
54
+ options.defaultMinute = now.getMinutes();
55
+ options.defaultSeconds = now.getSeconds();
56
+
57
+ options = await this.handleOptions(options);
58
+
59
+ this.instance = flatpickr(
60
+ this.querySelector<HTMLElement>(this.selector)!,
61
+ options
62
+ );
63
+
64
+ // If no value, set default time on open
65
+ this.instance.config.onOpen.push(() => {
66
+ if (this.instance.input.value === '') {
67
+ const now = new Date();
68
+ this.instance.jumpToDate(now);
69
+ this.instance.config.defaultHour = now.getHours();
70
+ this.instance.config.defaultMinute = now.getMinutes();
71
+ this.instance.config.defaultSeconds = now.getSeconds();
72
+ }
73
+ });
74
+
75
+ this.querySelector('[data-toggle]')?.addEventListener('click', () => {
76
+ setTimeout(() => {
77
+ this.querySelector<HTMLInputElement>('[data-input]')?.focus();
78
+ }, 0);
79
+ });
80
+ }
81
+
82
+ async handleOptions(options: flatpickr.Options.Options): Promise<flatpickr.Options.Options> {
83
+ options.plugins = options.plugins || [];
84
+
85
+ await Promise.all([
86
+ this.handleLocale(options),
87
+ this.handleMonthSelect(options)
88
+ ]);
89
+
90
+ return options;
91
+ }
92
+
93
+ private async handleLocale(options: Record<string, any>) {
94
+ if (this.locale) {
95
+ await useImport(`flatpickr/dist/l10n/${this.locale}.js`);
96
+
97
+ options.locale = this.locale.replace(/-/, '_');
98
+ }
99
+
100
+ return options;
101
+ }
102
+
103
+ private async handleMonthSelect(options: Record<string, any>) {
104
+ if (options.monthSelect) {
105
+ useCssImport('flatpickr/dist/plugins/monthSelect/style.css');
106
+ const { default: monthSelect } = await import('flatpickr/dist/plugins/monthSelect');
107
+
108
+ if (typeof options.monthSelect === 'boolean') {
109
+ options.monthSelect = {
110
+ shorthand: true,
111
+ dateFormat: 'Y-m',
112
+ altFormat: 'Y-m'
113
+ };
114
+ }
115
+
116
+ options.plugins.push(monthSelect(options.monthSelect));
117
+ }
118
+
119
+ return options;
120
+ }
121
+
122
+ getInstance() {
123
+ return this.instance;
124
+ }
125
+ }
126
+
127
+ customElements.define(FlatpickrElement.is, FlatpickrElement);
@@ -0,0 +1,174 @@
1
+ import type { IFrameModalElement } from './iframe-modal';
2
+ import { data } from '../data';
3
+ import { __, highlight, html, selectOne, slideUp } from '../service';
4
+ import { template } from 'lodash-es';
5
+
6
+ export type ModalSelectCallback = (item: any) => void;
7
+
8
+ export function createCallback(type: 'list' | 'single', selector: string, modalSelector: string): ModalSelectCallback {
9
+ switch (type) {
10
+ // case 'tag':
11
+ // return () => {
12
+ //
13
+ // };
14
+ case 'list':
15
+ return (item: any) => {
16
+ const modalList = document.querySelector(selector) as any as ModalListSelectElement;
17
+
18
+ if (!modalList.querySelector(`[data-value="${item.value}"]`)) {
19
+ modalList.appendItem(item, true);
20
+
21
+ selectOne<IFrameModalElement>(modalSelector)?.close();
22
+ } else {
23
+ alert(__('unicorn.field.modal.already.selected'));
24
+ }
25
+ };
26
+
27
+ case 'single':
28
+ default:
29
+ return (item) => {
30
+ const element = document.querySelector<HTMLDivElement>(selector)!;
31
+
32
+ const image = element.querySelector<HTMLDivElement>('[data-role=image]')!;
33
+ const title = element.querySelector<HTMLInputElement>('[data-role=title]')!;
34
+ const store = element.querySelector<HTMLInputElement>('[data-role=value]')!;
35
+
36
+ if (image && item.image) {
37
+ image.style.backgroundImage = `url(${item.image});`;
38
+ }
39
+
40
+ title.value = item.title || '';
41
+ store.value = item.value || '';
42
+
43
+ store.dispatchEvent(new CustomEvent('change'));
44
+
45
+ selectOne<IFrameModalElement>(modalSelector)?.close();
46
+
47
+ highlight(title);
48
+ };
49
+ }
50
+ }
51
+
52
+ interface ModalListOptions {
53
+ modalSelector: string;
54
+ itemTemplate: string;
55
+ sortable: boolean;
56
+ dataKey: string;
57
+ max: number;
58
+ }
59
+
60
+ class ModalListSelectElement extends HTMLElement {
61
+ static is = 'uni-modal-list';
62
+
63
+ itemTemplate!: ReturnType<typeof template>;
64
+ options!: ModalListOptions;
65
+
66
+ get listContainer() {
67
+ return this.querySelector<HTMLDivElement>('[data-role=list-container]')!;
68
+ }
69
+
70
+ get modal() {
71
+ return document.querySelector<IFrameModalElement>(this.options.modalSelector);
72
+ }
73
+
74
+ get items(): Element[] {
75
+ return Array.from(this.listContainer.children);
76
+ }
77
+
78
+ connectedCallback() {
79
+ this.options = JSON.parse(this.getAttribute('options') || '{}');
80
+ this.itemTemplate = template(document.querySelector(this.options.itemTemplate)!.innerHTML);
81
+
82
+ const emptyInput = this.querySelector<HTMLInputElement>('[data-role=empty]');
83
+
84
+ if (emptyInput) {
85
+ emptyInput.name = emptyInput.dataset.name || '';
86
+ }
87
+
88
+ if (this.options.sortable) {
89
+ import('sortablejs').then(({ default: Sortable }) => {
90
+ new Sortable(this.listContainer, { handle: '.h-drag-handle', animation: 150 });
91
+ });
92
+ }
93
+
94
+ const selectButton = this.querySelector<HTMLButtonElement>('[data-role=select]')!;
95
+ selectButton.addEventListener('click', (e) => {
96
+ this.open(e);
97
+ });
98
+
99
+ this.querySelector('[data-role=clear]')?.addEventListener('click', () => {
100
+ this.items.forEach((item) => {
101
+ item.querySelector<HTMLButtonElement>('[data-role=remove]')?.click();
102
+ });
103
+ });
104
+
105
+ selectButton.style.pointerEvents = '';
106
+
107
+ this.render();
108
+ }
109
+
110
+ render() {
111
+ const items: Record<string, any>[] = data('unicorn.modal-field')[this.options.dataKey] || [];
112
+
113
+ items.forEach((item) => {
114
+ this.appendItem(item);
115
+ });
116
+ }
117
+
118
+ appendItem(item: Record<string, any>, highlights = false) {
119
+ const itemHtml = html(this.itemTemplate({ item }));
120
+
121
+ itemHtml.dataset.value = item.value;
122
+ itemHtml.querySelector<HTMLButtonElement>('[data-role=remove]')?.addEventListener('click', () => {
123
+ slideUp(itemHtml).then(() => {
124
+ itemHtml.remove();
125
+ this.toggleRequired();
126
+ });
127
+ });
128
+
129
+ this.listContainer.appendChild(itemHtml);
130
+ this.toggleRequired();
131
+
132
+ if (highlights) {
133
+ highlight(itemHtml);
134
+ }
135
+ }
136
+
137
+ toggleRequired() {
138
+ const placeholder = this.querySelector<HTMLInputElement>('[data-role=validation-placeholder]');
139
+
140
+ if (placeholder) {
141
+ placeholder.disabled = this.listContainer.children.length !== 0;
142
+ }
143
+ }
144
+
145
+ open(event: Event) {
146
+ event.preventDefault();
147
+ event.stopPropagation();
148
+
149
+ const max = this.options.max;
150
+
151
+ const target = event.target as HTMLAnchorElement;
152
+
153
+ if (!max) {
154
+ this.modal?.open(target.href, { size: 'modal-xl' });
155
+ return;
156
+ }
157
+
158
+ if (this.listContainer.children.length >= max) {
159
+ alert(
160
+ __('unicorn.field.modal.max.selected', max)
161
+ );
162
+
163
+ return;
164
+ }
165
+
166
+ this.modal?.open(target.href, { size: 'modal-xl' });
167
+ }
168
+ }
169
+
170
+ customElements.define(ModalListSelectElement.is, ModalListSelectElement);
171
+
172
+ export interface ModalSelectModule {
173
+ createCallback: typeof createCallback;
174
+ }
@@ -0,0 +1,27 @@
1
+ import { data } from '../data';
2
+ import { useCssImport } from '../service';
3
+ import { createApp } from 'vue';
4
+ import ModalTreeApp from '../vue/components/ModalTree/ModalTreeApp.vue';
5
+ useCssImport('@vue-animate');
6
+
7
+ const app = createApp({
8
+ name: 'modal-tree',
9
+ components: {
10
+ ModalTreeApp
11
+ }
12
+ });
13
+ app.config.globalProperties.$getData = data;
14
+
15
+ class ModalTreeElement extends HTMLElement {
16
+ static is = 'modal-tree';
17
+
18
+ vm: any;
19
+
20
+ connectedCallback() {
21
+ if (!this.vm) {
22
+ this.vm = app.mount(this);
23
+ }
24
+ }
25
+ }
26
+
27
+ customElements.define(ModalTreeElement.is, ModalTreeElement);
@@ -0,0 +1,361 @@
1
+ import { Modal } from 'bootstrap';
2
+ import type { Options } from 'sortablejs';
3
+ import {
4
+ type ComponentPublicInstance,
5
+ computed,
6
+ createApp,
7
+ defineComponent,
8
+ getCurrentInstance,
9
+ nextTick,
10
+ onMounted,
11
+ PropType,
12
+ ref,
13
+ useTemplateRef
14
+ } from 'vue';
15
+ import { VueDraggable } from 'vue-draggable-plus';
16
+ import {
17
+ createItem,
18
+ ItemCard,
19
+ ItemCardPlaceholder,
20
+ MultiUploader,
21
+ MultiUploaderComposableInstance,
22
+ MultiUploaderOptions,
23
+ UploaderItem
24
+ } from 'vue-multi-uploader';
25
+ import css from 'vue-multi-uploader/src/vue-multi-uploader.scss?inline';
26
+ import { useStack } from '../composable';
27
+ import { data } from '../data';
28
+ import { forceArray, injectCssToDocument } from '../service';
29
+ import { mergeDeep } from '../utilities';
30
+
31
+ injectCssToDocument(css);
32
+
33
+ export type UniMultiUploaderOptions = {
34
+ value?: any[];
35
+ uploadUrl: string;
36
+ maxFiles?: number;
37
+ maxConcurrent?: number;
38
+ thumbSize?: number;
39
+ accept?: string;
40
+ readonly: boolean;
41
+ disabled: boolean;
42
+ fieldName?: string;
43
+ fieldFullName?: string;
44
+ tmplSelector: string;
45
+ canReplace: false;
46
+ openFileHandler?: (item: UploaderItem) => void;
47
+ }
48
+
49
+ const defaultOptions = {
50
+ readonly: false,
51
+ disabled: false,
52
+ sortable: false,
53
+ thumbSize: 150,
54
+ maxConcurrent: 5,
55
+ canReplace: false,
56
+ tmplSelector: '#multi-uploader-field-tmpl',
57
+ };
58
+
59
+ class MultiUploaderElement extends HTMLElement {
60
+ static is = 'uni-multi-uploader';
61
+
62
+ modalElement!: HTMLDivElement;
63
+ vm!: ComponentPublicInstance;
64
+
65
+ async connectedCallback() {
66
+ let options: Partial<UniMultiUploaderOptions> = JSON.parse(
67
+ this.getAttribute('options') || '{}'
68
+ );
69
+
70
+ const resolvedOptions: UniMultiUploaderOptions = mergeDeep({}, defaultOptions, options);
71
+
72
+ // Make some default options since PHP will send NULL
73
+ resolvedOptions.thumbSize ??= 150;
74
+
75
+ this.modalElement = this.querySelector<HTMLDivElement>('.modal')!;
76
+
77
+ const tmplSelector = resolvedOptions.tmplSelector;
78
+
79
+ const app = createApp(
80
+ createAppInstance(resolvedOptions, document.querySelector(tmplSelector)!.innerHTML, this)
81
+ );
82
+
83
+ this.vm = app.mount(this);
84
+ }
85
+ }
86
+
87
+ customElements.define(MultiUploaderElement.is, MultiUploaderElement);
88
+
89
+ function createAppInstance(opt: UniMultiUploaderOptions, tmpl: string, el: MultiUploaderElement) {
90
+ return defineComponent({
91
+ name: 'MultiUploaderFieldApp',
92
+ template: tmpl,
93
+ components: {
94
+ VueDraggable,
95
+ MultiUploader,
96
+ ItemCard,
97
+ ItemCardPlaceholder,
98
+ },
99
+ props: {
100
+ stackName: String as PropType<string>,
101
+ },
102
+ setup(props, { expose }) {
103
+ const options = ref<UniMultiUploaderOptions>(opt);
104
+ const current = ref<UploaderItem>();
105
+ const currentIndex = ref<number>();
106
+ const loading = ref(false);
107
+ const dragarea = ref<HTMLDivElement>();
108
+ const modal = ref<HTMLDivElement>();
109
+ const app = getCurrentInstance();
110
+ const uploader = useTemplateRef<typeof MultiUploader>('uploader');
111
+ const canModify = computed(() => !options.value.disabled && !options.value.readonly);
112
+ const instance = ref<MultiUploaderComposableInstance>();
113
+
114
+ onMounted(() => {
115
+ instance.value = uploader.value!.instance;
116
+
117
+ domEmit('multi-uploader:mounted', { app, uploader });
118
+ });
119
+
120
+ const items = ref<UploaderItem[]>([]);
121
+
122
+ for (let item of forceArray(options.value.value)) {
123
+ if (typeof item === 'string') {
124
+ item = {
125
+ url: item
126
+ };
127
+ }
128
+
129
+ const uploadItem = createItem({
130
+ url: item.url || '',
131
+ thumbUrl: item.thumbUrl || item.thumb_url || item.url || '',
132
+ data: item
133
+ });
134
+
135
+ items.value.push(uploadItem);
136
+ }
137
+
138
+ const uploadUrl = options.value.uploadUrl;
139
+ const value = items.value;
140
+ const uploaderOptions = ref<MultiUploaderOptions>({
141
+ maxFiles: () => options.value.maxFiles,
142
+ readonly: () => options.value.readonly,
143
+ disabled: () => options.value.disabled,
144
+ accept: () => options.value.accept,
145
+ maxConcurrent: () => options.value.maxConcurrent,
146
+ prepareXhr(xhr) {
147
+ xhr.setRequestHeader(
148
+ 'X-CSRF-TOKEN',
149
+ data('csrf-token')
150
+ );
151
+ },
152
+ onItemUploadSuccess(item, xhr) {
153
+ const res = JSON.parse(xhr.responseText);
154
+ item.url = res.data.url;
155
+ item.thumbUrl = res.data.thumbUrl || res.data.thumb_url || res.data.url;
156
+ item.data = res.data;
157
+ item.data.title ??= item.url.split('/').pop()?.split('?').shift() || '';
158
+ }
159
+ });
160
+ const draggableOptions: Options = {
161
+ draggable: '.c-drag-item',
162
+ animation: 300,
163
+ disabled: !canModify.value,
164
+ };
165
+
166
+ function openFile(item: UploaderItem) {
167
+ if (options.value.openFileHandler) {
168
+ options.value.openFileHandler(item);
169
+ } else {
170
+ window.open(item.download_url || item.url);
171
+ }
172
+ }
173
+
174
+ async function itemClick(item: UploaderItem, i: number, event: MouseEvent) {
175
+ current.value = item;
176
+ currentIndex.value = i;
177
+
178
+ domEmit('item-click', { item, i });
179
+
180
+ nextTick().then(() => {
181
+ Modal.getOrCreateInstance(modal.value!).show();
182
+ });
183
+ // this.$options.metaModal.modal('show');
184
+ }
185
+
186
+ function metaSave() {
187
+ current.value = undefined;
188
+ currentIndex.value = undefined;
189
+
190
+ // nextTick().then(() => {
191
+ // new bootstrap.Modal(modal.value).hide();
192
+ // });
193
+ // this.$options.metaModal.modal('hide');
194
+ }
195
+
196
+ function isImage(url: string) {
197
+ const ext = url.split('.').pop()!.split('?').shift() || '';
198
+ const allow = ['png', 'jpeg', 'jpg', 'gif', 'bmp', 'webp'];
199
+
200
+ return allow.indexOf(ext.toLowerCase()) !== -1;
201
+ }
202
+
203
+ function dragover(e: DragEvent) {
204
+ if (!options.value.canReplace) {
205
+ return;
206
+ }
207
+
208
+ dragarea.value!.style.opacity = '0.75';
209
+ }
210
+
211
+ function dragleave(e: DragEvent) {
212
+ if (!options.value.canReplace) {
213
+ return;
214
+ }
215
+
216
+ dragarea.value!.style.opacity = '1';
217
+ }
218
+
219
+ function drop(event: DragEvent) {
220
+ if (!options.value.canReplace) {
221
+ return;
222
+ }
223
+
224
+ dragarea.value!.style.opacity = '1';
225
+ const item = current.value;
226
+ const file = event.dataTransfer?.files[0] || null;
227
+
228
+ if (!file) {
229
+ return;
230
+ }
231
+
232
+ instance.value!.checkFile(file);
233
+
234
+ if (instance.value!.isReadonly) {
235
+ return;
236
+ }
237
+
238
+ item!.file = file;
239
+
240
+ loading.value = true;
241
+
242
+ try {
243
+ instance.value!.uploadFile(item!);
244
+ } finally {
245
+ loading.value = false;
246
+ }
247
+ }
248
+
249
+ function uploading() {
250
+ useStack(props.stackName).push(true);
251
+
252
+ domEmit('uploading');
253
+ }
254
+
255
+ function uploaded() {
256
+ useStack(props.stackName).pop();
257
+
258
+ domEmit('uploaded');
259
+ }
260
+
261
+ function onChange(item: UploaderItem) {
262
+ domEmit('change', item);
263
+ }
264
+
265
+ function domEmit(event: string, detail?: any) {
266
+ el.dispatchEvent(new CustomEvent(event, { detail }));
267
+ }
268
+
269
+ const foo = ref<string>();
270
+
271
+ foo.value = 'Bar';
272
+
273
+ const icons = ref<Record<string, string>>({
274
+ default: 'fas fa-file',
275
+ pdf: 'fas fa-file-pdf text-danger',
276
+ xls: 'fas fa-file-excel text-success',
277
+ xlsx: 'fas fa-file-excel text-success',
278
+ doc: 'fas fa-file-word text-primary',
279
+ docx: 'fas fa-file-word text-primary',
280
+ ppt: 'fas fa-file-powerpoint text-warning',
281
+ pptx: 'fas fa-file-powerpoint text-warning',
282
+ zip: 'fas fa-file-archive text-dark',
283
+ '7z': 'fas fa-file-archive text-dark',
284
+ rar: 'fas fa-file-archive text-dark',
285
+ mp4: 'fas fa-file-video text-dark',
286
+ avi: 'fas fa-file-video text-dark',
287
+ flv: 'fas fa-file-video text-dark',
288
+ mov: 'fas fa-file-video text-dark',
289
+ ogg: 'fas fa-file-video text-dark',
290
+ webm: 'fas fa-file-video text-dark',
291
+ mpg: 'fas fa-file-video text-dark',
292
+ mp3: 'fas fa-file-audio text-dark',
293
+ acc: 'fas fa-file-audio text-dark',
294
+ wav: 'fas fa-file-audio text-dark',
295
+ });
296
+
297
+ function setIcons(newIcons: Record<string, string>, merge = true) {
298
+ if (merge) {
299
+ icons.value = { ...icons.value, ...newIcons };
300
+ return;
301
+ }
302
+
303
+ icons.value = newIcons;
304
+ }
305
+
306
+ expose({
307
+ uploader,
308
+ instance,
309
+ value,
310
+ canModify,
311
+ openFile,
312
+ itemClick,
313
+ isImage,
314
+ setIcons,
315
+ });
316
+
317
+ function fileIcon(item: UploaderItem) {
318
+ let path = item.file ? item.file.name : item.url;
319
+
320
+ // strip query
321
+ path = String(path).split('?')[0];
322
+
323
+ // Get extension
324
+ const ext = path.split('.').pop() || '';
325
+
326
+ const def = 'default' in icons ? icons.default : 'fas fa-file';
327
+
328
+ return icons[String(ext || 'default').toLowerCase() as keyof typeof icons] || def;
329
+ }
330
+
331
+ return {
332
+ uploader,
333
+ uploadUrl,
334
+ value,
335
+ uploaderOptions,
336
+ draggableOptions,
337
+ modal,
338
+ dragarea,
339
+ options,
340
+ current,
341
+ currentIndex,
342
+ loading,
343
+ instance,
344
+ canModify,
345
+
346
+ openFile,
347
+ itemClick,
348
+ metaSave,
349
+ isImage,
350
+ dragover,
351
+ dragleave,
352
+ drop,
353
+ uploading,
354
+ uploaded,
355
+ onChange,
356
+ domEmit,
357
+ fileIcon,
358
+ };
359
+ }
360
+ });
361
+ }