datafolder 1.0.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 (182) hide show
  1. package/README.md +169 -0
  2. package/index.js +3 -0
  3. package/package.json +27 -0
  4. package/sample.js +37 -0
  5. package/scripts/airbnb.js +73 -0
  6. package/scripts/airbnb2.js +74 -0
  7. package/scripts/airbnb2_queries.js +231 -0
  8. package/scripts/airbnb_queries.js +264 -0
  9. package/scripts/downloadFile.js +42 -0
  10. package/scripts/express.js +46 -0
  11. package/scripts/fastify.js +79 -0
  12. package/scripts/flights.js +75 -0
  13. package/scripts/flights2.js +81 -0
  14. package/scripts/flights2_queries.js +453 -0
  15. package/scripts/flights_queries.js +403 -0
  16. package/src/QyAcl.js +252 -0
  17. package/src/QyAclSaver.js +14 -0
  18. package/src/QyAclSaver_Worker.js +29 -0
  19. package/src/QyAclWriter.js +19 -0
  20. package/src/QyBatch.js +501 -0
  21. package/src/QyBinWriter.js +140 -0
  22. package/src/QyCD.js +61 -0
  23. package/src/QyCache.js +212 -0
  24. package/src/QyCompressor.js +60 -0
  25. package/src/QyCompressor_Worker.js +13 -0
  26. package/src/QyDB.js +122 -0
  27. package/src/QyDefaultOptions.js +59 -0
  28. package/src/QyDir.js +192 -0
  29. package/src/QyFetch.js +38 -0
  30. package/src/QyFile.js +124 -0
  31. package/src/QyFileContentUtils.js +175 -0
  32. package/src/QyFileLogger.js +58 -0
  33. package/src/QyIndex.js +237 -0
  34. package/src/QyIndexPropTree.js +92 -0
  35. package/src/QyIntersectNode.js +48 -0
  36. package/src/QyItem.js +105 -0
  37. package/src/QyJsonSaver.js +20 -0
  38. package/src/QyKVData.js +289 -0
  39. package/src/QyKVDataCleaner.js +17 -0
  40. package/src/QyKVDataCleaner_Worker.js +94 -0
  41. package/src/QyKVDataLoader.js +65 -0
  42. package/src/QyKVDataSaver.js +17 -0
  43. package/src/QyKVDataSaver_Worker.js +81 -0
  44. package/src/QyLogFileSaver.js +14 -0
  45. package/src/QyLogFileSaver_Worker.js +26 -0
  46. package/src/QyLogger.js +76 -0
  47. package/src/QyMessageWorker.js +41 -0
  48. package/src/QyMessager.js +84 -0
  49. package/src/QyMessager_Run.js +13 -0
  50. package/src/QyMod.js +15 -0
  51. package/src/QyOpRunner.js +73 -0
  52. package/src/QyQueue.js +60 -0
  53. package/src/QySaver.js +83 -0
  54. package/src/QySnapshotCompactor.js +17 -0
  55. package/src/QySnapshotCompactor_Worker.js +151 -0
  56. package/src/QySnapshotSaver.js +17 -0
  57. package/src/QySnapshotSaver_Worker.js +164 -0
  58. package/src/QySnapshots.js +354 -0
  59. package/src/QyStructure.js +50 -0
  60. package/src/QyTrigger.js +133 -0
  61. package/src/QyUnionNode.js +105 -0
  62. package/src/QyUtils.js +250 -0
  63. package/src/QyWorker.js +33 -0
  64. package/src/hackernews.js +93 -0
  65. package/src/rpc/evalCode.js +9 -0
  66. package/src/rpc/getFileContent.js +16 -0
  67. package/src/rpc/getSubItems.js +32 -0
  68. package/src/rpc/queryFiles.js +33 -0
  69. package/src/rpc/reloadRpc.js +1 -0
  70. package/src/simulate.js +114 -0
  71. package/src/simulate2.js +110 -0
  72. package/src/soc_pokec.js +43 -0
  73. package/static/bootstrap-5.3.8-dist/css/bootstrap.min.css +6 -0
  74. package/static/bootstrap-5.3.8-dist/js/bootstrap.bundle.js +6312 -0
  75. package/static/datafolderui/QyStyledElement.js +30 -0
  76. package/static/datafolderui/QyUIFolderTree.js +203 -0
  77. package/static/datafolderui/QyUIUtils.js +42 -0
  78. package/static/datafolderui/display-tree.js +108 -0
  79. package/static/datafolderui/explore-tree.js +122 -0
  80. package/static/datafolderui/folder-tree.js +5 -0
  81. package/static/datafolderui/hscrpt.js +25 -0
  82. package/static/datafolderui/index.css +6 -0
  83. package/static/datafolderui/index.html +20 -0
  84. package/static/datafolderui/json-view.js +74 -0
  85. package/static/datafolderui/kv-input.js +66 -0
  86. package/static/datafolderui/list-input.js +52 -0
  87. package/static/datafolderui/main-page.js +98 -0
  88. package/static/datafolderui/num-input.js +27 -0
  89. package/static/datafolderui/query-editor.js +229 -0
  90. package/static/datafolderui/querySelectorShadowRoot.js +24 -0
  91. package/static/datafolderui/resizable-input.js +67 -0
  92. package/static/favicon.ico +0 -0
  93. package/static/icons-1.13.1/font/bootstrap-icons.min.css +5 -0
  94. package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff +0 -0
  95. package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
  96. package/static/json-view/dist/jsonview.js +433 -0
  97. package/static/tree.js/tree.css +106 -0
  98. package/static/tree.js/tree.js +374 -0
  99. package/static/w2ui/dist/w2ui.min.css +2 -0
  100. package/static/w2ui/src/less/icons/icons.json +1 -0
  101. package/static/w2ui/src/less/icons/preview.html +33 -0
  102. package/static/w2ui/src/less/icons/readme.md +15 -0
  103. package/static/w2ui/src/less/icons/svg/box.svg +3 -0
  104. package/static/w2ui/src/less/icons/svg/check.svg +3 -0
  105. package/static/w2ui/src/less/icons/svg/colors.svg +3 -0
  106. package/static/w2ui/src/less/icons/svg/columns.svg +5 -0
  107. package/static/w2ui/src/less/icons/svg/cross.svg +4 -0
  108. package/static/w2ui/src/less/icons/svg/drop.svg +6 -0
  109. package/static/w2ui/src/less/icons/svg/empty.svg +3 -0
  110. package/static/w2ui/src/less/icons/svg/eye-dropper.svg +4 -0
  111. package/static/w2ui/src/less/icons/svg/info.svg +8 -0
  112. package/static/w2ui/src/less/icons/svg/paste.svg +4 -0
  113. package/static/w2ui/src/less/icons/svg/pencil.svg +5 -0
  114. package/static/w2ui/src/less/icons/svg/plus.svg +3 -0
  115. package/static/w2ui/src/less/icons/svg/reload.svg +5 -0
  116. package/static/w2ui/src/less/icons/svg/search.svg +3 -0
  117. package/static/w2ui/src/less/icons/svg/settings.svg +8 -0
  118. package/static/w2ui/src/less/icons/w2ui-font.css +36 -0
  119. package/static/w2ui/src/less/icons/w2ui-font.woff +0 -0
  120. package/static/w2ui/src/less/src/buttons.less +184 -0
  121. package/static/w2ui/src/less/src/common.less +541 -0
  122. package/static/w2ui/src/less/src/fields.less +302 -0
  123. package/static/w2ui/src/less/src/form.less +319 -0
  124. package/static/w2ui/src/less/src/grid.less +1057 -0
  125. package/static/w2ui/src/less/src/icons.less +37 -0
  126. package/static/w2ui/src/less/src/layout.less +86 -0
  127. package/static/w2ui/src/less/src/mixins.less +87 -0
  128. package/static/w2ui/src/less/src/popup.less +277 -0
  129. package/static/w2ui/src/less/src/sidebar.less +343 -0
  130. package/static/w2ui/src/less/src/tabs.less +145 -0
  131. package/static/w2ui/src/less/src/toolbar.less +219 -0
  132. package/static/w2ui/src/less/src/tooltip.less +948 -0
  133. package/static/w2ui/src/less/w2ui.less +196 -0
  134. package/static/w2ui/src/locale/az-az.json +114 -0
  135. package/static/w2ui/src/locale/ba-ba.json +114 -0
  136. package/static/w2ui/src/locale/bg-bg.json +114 -0
  137. package/static/w2ui/src/locale/ca-es.json +114 -0
  138. package/static/w2ui/src/locale/da-dk.json +114 -0
  139. package/static/w2ui/src/locale/de-de.json +114 -0
  140. package/static/w2ui/src/locale/en-gb.json +17 -0
  141. package/static/w2ui/src/locale/en-us.json +17 -0
  142. package/static/w2ui/src/locale/es-ar.json +115 -0
  143. package/static/w2ui/src/locale/es-cr.json +119 -0
  144. package/static/w2ui/src/locale/es-es.json +114 -0
  145. package/static/w2ui/src/locale/es-mx.json +114 -0
  146. package/static/w2ui/src/locale/fr-fr.json +114 -0
  147. package/static/w2ui/src/locale/gl-es.json +114 -0
  148. package/static/w2ui/src/locale/hr-hr.json +114 -0
  149. package/static/w2ui/src/locale/hu-hu.json +114 -0
  150. package/static/w2ui/src/locale/id-id.json +114 -0
  151. package/static/w2ui/src/locale/it-it.json +114 -0
  152. package/static/w2ui/src/locale/ja-jp.json +114 -0
  153. package/static/w2ui/src/locale/kk-kz.json +116 -0
  154. package/static/w2ui/src/locale/ko-kr.json +114 -0
  155. package/static/w2ui/src/locale/lt-lt.json +114 -0
  156. package/static/w2ui/src/locale/nl-nl.json +114 -0
  157. package/static/w2ui/src/locale/no-no.json +114 -0
  158. package/static/w2ui/src/locale/pl-pl.json +114 -0
  159. package/static/w2ui/src/locale/pt-br.json +114 -0
  160. package/static/w2ui/src/locale/ru-ru.json +116 -0
  161. package/static/w2ui/src/locale/sk-sk.json +114 -0
  162. package/static/w2ui/src/locale/sl-si.json +116 -0
  163. package/static/w2ui/src/locale/sr-rs.json +114 -0
  164. package/static/w2ui/src/locale/tr-tr.json +114 -0
  165. package/static/w2ui/src/locale/uk-ua.json +114 -0
  166. package/static/w2ui/src/locale/zh-cn.json +114 -0
  167. package/static/w2ui/src/locale/zh-tw.json +114 -0
  168. package/static/w2ui/src/query.js +583 -0
  169. package/static/w2ui/src/readme.md +11 -0
  170. package/static/w2ui/src/w2base.js +311 -0
  171. package/static/w2ui/src/w2compat.js +207 -0
  172. package/static/w2ui/src/w2field.js +1854 -0
  173. package/static/w2ui/src/w2form.js +2411 -0
  174. package/static/w2ui/src/w2grid.js +8916 -0
  175. package/static/w2ui/src/w2layout.js +1194 -0
  176. package/static/w2ui/src/w2locale.js +129 -0
  177. package/static/w2ui/src/w2popup.js +909 -0
  178. package/static/w2ui/src/w2sidebar.js +1769 -0
  179. package/static/w2ui/src/w2tabs.js +669 -0
  180. package/static/w2ui/src/w2toolbar.js +1180 -0
  181. package/static/w2ui/src/w2tooltip.js +3134 -0
  182. package/static/w2ui/src/w2utils.js +2457 -0
@@ -0,0 +1,669 @@
1
+ /**
2
+ * Part of w2ui 2.0 library
3
+ * - Dependencies: mQuery, w2utils, w2base, w2tooltip
4
+ *
5
+ * == 2.0 changes
6
+ * - CSP - fixed inline events
7
+ * - removed jQuery dependency
8
+ * - observeResize for the box
9
+ * - refactored w2events
10
+ * - scrollIntoView - removed callback
11
+ * - scroll, scrollIntoView return promise
12
+ * - animateInsert, animateClose - returns a promise
13
+ * - add, insert return a promise
14
+ * - onMouseEnter, onMouseLeave, onMouseDown, onMouseUp
15
+ */
16
+
17
+ import { w2base } from './w2base.js'
18
+ import { w2ui, w2utils } from './w2utils.js'
19
+ import { query } from './query.js'
20
+ import { w2tooltip } from './w2tooltip.js'
21
+
22
+ class w2tabs extends w2base {
23
+ constructor(options) {
24
+ super(options.name)
25
+ this.box = null // DOM Element that holds the element
26
+ this.name = null // unique name for w2ui
27
+ this.active = null
28
+ this.reorder = false
29
+ this.flow = 'down' // can be down or up
30
+ this.tooltip = 'top|left' // can be top, bottom, left, right
31
+ this.tabs = []
32
+ this.routeData = {} // data for dynamic routes
33
+ this.last = {} // placeholder for internal variables
34
+ this.right = ''
35
+ this.style = ''
36
+ this.onClick = null
37
+ this.onMouseEnter = null // mouse enter and lease
38
+ this.onMouseLeave = null
39
+ this.onMouseDown = null
40
+ this.onMouseUp = null
41
+ this.onClose = null
42
+ this.onRender = null
43
+ this.onRefresh = null
44
+ this.onResize = null
45
+ this.onDestroy = null
46
+ this.tab_template = {
47
+ id: null,
48
+ text: null,
49
+ icon: null,
50
+ route: null,
51
+ hidden: false,
52
+ disabled: false,
53
+ closable: false,
54
+ tooltip: null,
55
+ style: '',
56
+ onClick: null,
57
+ onRefresh: null,
58
+ onClose: null
59
+ }
60
+ let tabs = options.tabs
61
+ delete options.tabs
62
+ // mix in options
63
+ Object.assign(this, options)
64
+ // add item via method to makes sure item_template is applied
65
+ if (Array.isArray(tabs)) this.add(tabs)
66
+ // need to reassign back to keep it in config
67
+ options.tabs = tabs
68
+
69
+ // render if box specified
70
+ if (typeof this.box == 'string') this.box = query(this.box).get(0)
71
+ if (this.box) this.render(this.box)
72
+ }
73
+
74
+ add(tab) {
75
+ return this.insert(null, tab)
76
+ }
77
+
78
+ insert(id, tabs) {
79
+ if (!Array.isArray(tabs)) tabs = [tabs]
80
+ // assume it is array
81
+ let proms = []
82
+ tabs.forEach(tab => {
83
+ // checks
84
+ if (tab.id == null) {
85
+ console.log(`ERROR: The parameter "id" is required but not supplied. (obj: ${this.name})`)
86
+ return
87
+ }
88
+ if (!w2utils.checkUniqueId(tab.id, this.tabs, 'tabs', this.name)) return
89
+ // add tab
90
+ let it = Object.assign({}, this.tab_template, tab)
91
+ if (id == null) {
92
+ this.tabs.push(it)
93
+ proms.push(this.animateInsert(null, it))
94
+ } else {
95
+ let middle = this.get(id, true)
96
+ let before = this.tabs[middle].id
97
+ this.tabs.splice(middle, 0, it)
98
+ proms.push(this.animateInsert(before, it))
99
+ }
100
+ })
101
+ return Promise.all(proms)
102
+ }
103
+
104
+ remove() {
105
+ let effected = 0
106
+ Array.from(arguments).forEach(it => {
107
+ let tab = this.get(it)
108
+ if (!tab) return
109
+ effected++
110
+ // remove from array
111
+ this.tabs.splice(this.get(tab.id, true), 1)
112
+ // remove from screen
113
+ query(this.box).find(`#tabs_${this.name}_tab_${w2utils.escapeId(tab.id)}`).remove()
114
+ })
115
+ this.resize()
116
+ return effected
117
+ }
118
+
119
+ select(id) {
120
+ if (this.active == id || this.get(id) == null) return false
121
+ this.active = id
122
+ this.refresh()
123
+ return true
124
+ }
125
+
126
+ set(id, tab) {
127
+ let index = this.get(id, true)
128
+ if (index == null) return false
129
+ w2utils.extend(this.tabs[index], tab)
130
+ this.refresh(id)
131
+ return true
132
+ }
133
+
134
+ get(id, returnIndex) {
135
+ if (arguments.length === 0) {
136
+ let all = []
137
+ for (let i1 = 0; i1 < this.tabs.length; i1++) {
138
+ if (this.tabs[i1].id != null) {
139
+ all.push(this.tabs[i1].id)
140
+ }
141
+ }
142
+ return all
143
+ } else {
144
+ for (let i2 = 0; i2 < this.tabs.length; i2++) {
145
+ if (this.tabs[i2].id == id) { // need to be == since id can be numeric
146
+ return (returnIndex === true ? i2 : this.tabs[i2])
147
+ }
148
+ }
149
+ }
150
+ return null
151
+ }
152
+
153
+ show() {
154
+ let effected = []
155
+ Array.from(arguments).forEach(it => {
156
+ let tab = this.get(it)
157
+ if (!tab || tab.hidden === false) return
158
+ tab.hidden = false
159
+ effected.push(tab.id)
160
+ })
161
+ setTimeout(() => { effected.forEach(it => { this.refresh(it); this.resize() }) }, 15) // needs timeout
162
+ return effected
163
+ }
164
+
165
+ hide() {
166
+ let effected = []
167
+ Array.from(arguments).forEach(it => {
168
+ let tab = this.get(it)
169
+ if (!tab || tab.hidden === true) return
170
+ tab.hidden = true
171
+ effected.push(tab.id)
172
+ })
173
+ setTimeout(() => { effected.forEach(it => { this.refresh(it); this.resize() }) }, 15) // needs timeout
174
+ return effected
175
+ }
176
+
177
+ enable() {
178
+ let effected = []
179
+ Array.from(arguments).forEach(it => {
180
+ let tab = this.get(it)
181
+ if (!tab || tab.disabled === false) return
182
+ tab.disabled = false
183
+ effected.push(tab.id)
184
+ })
185
+ setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout
186
+ return effected
187
+ }
188
+
189
+ disable() {
190
+ let effected = []
191
+ Array.from(arguments).forEach(it => {
192
+ let tab = this.get(it)
193
+ if (!tab || tab.disabled === true) return
194
+ tab.disabled = true
195
+ effected.push(tab.id)
196
+ })
197
+ setTimeout(() => { effected.forEach(it => { this.refresh(it) }) }, 15) // needs timeout
198
+ return effected
199
+ }
200
+
201
+ dragMove(event) {
202
+ if (!this.last.reordering) return
203
+ let self = this
204
+ let info = this.last.moving
205
+ let tab = this.tabs[info.index]
206
+ let next = _find(info.index, 1)
207
+ let prev = _find(info.index, -1)
208
+ let $el = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(tab.id))
209
+ if (info.divX > 0 && next) {
210
+ let $nextEl = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(next.id))
211
+ let width1 = parseInt($el.get(0).clientWidth)
212
+ let width2 = parseInt($nextEl.get(0).clientWidth)
213
+ if (width1 < width2) {
214
+ width1 = Math.floor(width1 / 3)
215
+ width2 = width2 - width1
216
+ } else {
217
+ width1 = Math.floor(width2 / 3)
218
+ width2 = width2 - width1
219
+ }
220
+ if (info.divX > width2) {
221
+ let index = this.tabs.indexOf(next)
222
+ this.tabs.splice(info.index, 0, this.tabs.splice(index, 1)[0]) // reorder in the array
223
+ info.$tab.before($nextEl.get(0))
224
+ info.$tab.css('opacity', 0)
225
+ Object.assign(this.last.moving, {
226
+ index: index,
227
+ divX: -width1,
228
+ x: event.pageX + width1,
229
+ left: info.left + info.divX + width1
230
+ })
231
+ return
232
+ }
233
+ }
234
+ if (info.divX < 0 && prev) {
235
+ let $prevEl = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(prev.id))
236
+ let width1 = parseInt($el.get(0).clientWidth)
237
+ let width2 = parseInt($prevEl.get(0).clientWidth)
238
+ if (width1 < width2) {
239
+ width1 = Math.floor(width1 / 3)
240
+ width2 = width2 - width1
241
+ } else {
242
+ width1 = Math.floor(width2 / 3)
243
+ width2 = width2 - width1
244
+ }
245
+ if (Math.abs(info.divX) > width2) {
246
+ let index = this.tabs.indexOf(prev)
247
+ this.tabs.splice(info.index, 0, this.tabs.splice(index, 1)[0]) // reorder in the array
248
+ $prevEl.before(info.$tab)
249
+ info.$tab.css('opacity', 0)
250
+ Object.assign(info, {
251
+ index: index,
252
+ divX: width1,
253
+ x: event.pageX - width1,
254
+ left: info.left + info.divX - width1
255
+ })
256
+ return
257
+ }
258
+ }
259
+ function _find(ind, inc) {
260
+ ind += inc
261
+ let tab = self.tabs[ind]
262
+ if (tab && tab.hidden) {
263
+ tab = _find(ind, inc)
264
+ }
265
+ return tab
266
+ }
267
+ }
268
+
269
+ mouseAction(action, id, event) {
270
+ let tab = this.get(id)
271
+ let edata = this.trigger('mouse' + action, { target: id, tab, object: tab, originalEvent: event })
272
+ if (edata.isCancelled === true || tab?.disabled || tab?.hidden) return
273
+ switch (action) {
274
+ case 'Enter':
275
+ this.tooltipShow(id)
276
+ break
277
+ case 'Leave':
278
+ this.tooltipHide(id)
279
+ break
280
+ case 'Down':
281
+ this.initReorder(id, event)
282
+ break
283
+ case 'Up':
284
+ break
285
+ }
286
+ edata.finish()
287
+ }
288
+
289
+ tooltipShow(id) {
290
+ let tab = this.get(id)
291
+ let el = query(this.box).find('#tabs_'+ this.name + '_tab_'+ w2utils.escapeId(id)).get(0)
292
+ if (this.tooltip == null || tab?.disabled || this.last.reordering) {
293
+ return
294
+ }
295
+ let pos = this.tooltip
296
+ let txt = tab?.tooltip
297
+ if (typeof txt == 'function') txt = txt.call(this, tab)
298
+ w2tooltip.show({
299
+ anchor: el,
300
+ name: this.name + '_tooltip',
301
+ html: txt,
302
+ position: pos
303
+ })
304
+ }
305
+
306
+ tooltipHide(id) {
307
+ if (this.tooltip == null) return
308
+ w2tooltip.hide(this.name + '_tooltip')
309
+ }
310
+
311
+ getTabHTML(id) {
312
+ let index = this.get(id, true)
313
+ let tab = this.tabs[index]
314
+ if (tab == null) return false
315
+ if (tab.text == null && tab.caption != null) tab.text = tab.caption
316
+ if (tab.tooltip == null && tab.hint != null) tab.tooltip = tab.hint // for backward compatibility
317
+ if (tab.caption != null) {
318
+ console.log('NOTICE: tabs tab.caption property is deprecated, please use tab.text. Tab -> ', tab)
319
+ }
320
+ if (tab.hint != null) {
321
+ console.log('NOTICE: tabs tab.hint property is deprecated, please use tab.tooltip. Tab -> ', tab)
322
+ }
323
+
324
+ let text = tab.text
325
+ if (typeof text == 'function') text = text.call(this, tab)
326
+ if (text == null) text = ''
327
+
328
+ let closable = ''
329
+ let addStyle = ''
330
+ if (tab.hidden) { addStyle += 'display: none;' }
331
+ if (tab.disabled) { addStyle += 'opacity: 0.2;' }
332
+ if (tab.closable && !tab.disabled) {
333
+ closable = `<div class="w2ui-tab-close w2ui-eaction ${this.active === tab.id ? 'active' : ''}"
334
+ data-mousedown="stop" data-mouseup="clickClose|${tab.id}|event">
335
+ </div>`
336
+ }
337
+ let icon = ''
338
+ if (tab.icon) {
339
+ icon = `<span class="w2ui-tab-icon ${tab.icon}"></span>`
340
+ }
341
+ return `
342
+ <div id="tabs_${this.name}_tab_${tab.id}" style="${addStyle} ${tab.style}"
343
+ class="w2ui-tab w2ui-eaction ${this.active === tab.id ? 'active' : ''} ${tab.closable ? 'closable' : ''} ${tab.class ? tab.class : ''}"
344
+ data-mouseenter="mouseAction|Enter|${tab.id}|event]"
345
+ data-mouseleave="mouseAction|Leave|${tab.id}|event]"
346
+ data-mousedown="mouseAction|Down|${tab.id}|event"
347
+ data-mouseup="mouseAction|Up|${tab.id}|event"
348
+ data-click="click|${tab.id}|event">
349
+ ${icon + w2utils.lang(text) + closable}
350
+ </div>`
351
+ }
352
+
353
+ refresh(id) {
354
+ let time = Date.now()
355
+ if (this.flow == 'up') {
356
+ query(this.box).addClass('w2ui-tabs-up')
357
+ } else {
358
+ query(this.box).removeClass('w2ui-tabs-up')
359
+ }
360
+ // event before
361
+ let edata = this.trigger('refresh', { target: (id != null ? id : this.name), object: this.get(id) })
362
+ if (edata.isCancelled === true) return
363
+ if (id == null) {
364
+ // refresh all
365
+ for (let i = 0; i < this.tabs.length; i++) {
366
+ this.refresh(this.tabs[i].id)
367
+ }
368
+ } else {
369
+ // create or refresh only one item
370
+ let selector = '#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id)
371
+ let $tab = query(this.box).find(selector)
372
+ let tabHTML = this.getTabHTML(id)
373
+ if ($tab.length === 0) {
374
+ query(this.box).find('#tabs_'+ this.name +'_right').before(tabHTML)
375
+ } else {
376
+ if (query(this.box).find('.tab-animate-insert').length == 0) {
377
+ $tab.replace(tabHTML)
378
+ }
379
+ }
380
+ w2utils.bindEvents(query(this.box).find(`${selector}, ${selector} .w2ui-eaction`), this)
381
+ }
382
+ // right html
383
+ query(this.box).find('#tabs_'+ this.name +'_right').html(this.right)
384
+ // event after
385
+ edata.finish()
386
+ // this.resize();
387
+ return Date.now() - time
388
+ }
389
+
390
+ render(box) {
391
+ let time = Date.now()
392
+ if (typeof box == 'string') box = query(box).get(0)
393
+ // event before
394
+ let edata = this.trigger('render', { target: this.name, box: box ?? this.box })
395
+ if (edata.isCancelled === true) return
396
+ // default action
397
+ if (box != null) {
398
+ this.unmount() // clean previous control
399
+ this.box = box
400
+ }
401
+ if (!this.box) return false
402
+ // render all buttons
403
+ let html =`
404
+ <div class="w2ui-tabs-line"></div>
405
+ <div class="w2ui-scroll-wrapper w2ui-eaction" data-mousedown="resize">
406
+ <div id="tabs_${this.name}_right" class="w2ui-tabs-right">${this.right}</div>
407
+ </div>
408
+ <div class="w2ui-scroll-left w2ui-eaction" data-click='["scroll","left"]'></div>
409
+ <div class="w2ui-scroll-right w2ui-eaction" data-click='["scroll","right"]'></div>`
410
+ query(this.box)
411
+ .attr('name', this.name)
412
+ .addClass('w2ui-reset w2ui-tabs')
413
+ .html(html)
414
+ if (query(this.box).length > 0) {
415
+ query(this.box)[0].style.cssText += this.style
416
+ }
417
+ w2utils.bindEvents(query(this.box).find('.w2ui-eaction'), this)
418
+ // observe div resize
419
+ this.last.observeResize = new ResizeObserver(() => { this.resize() })
420
+ this.last.observeResize.observe(this.box)
421
+ // event after
422
+ edata.finish()
423
+ this.refresh()
424
+ this.resize()
425
+ return Date.now() - time
426
+ }
427
+
428
+ initReorder(id, event) {
429
+ if (!this.reorder) return
430
+ let self = this
431
+ let $tab = query(this.box).find('#tabs_' + this.name + '_tab_' + w2utils.escapeId(id))
432
+ let tabIndex = this.get(id, true)
433
+ let $ghost = query($tab.get(0).cloneNode(true))
434
+ let edata
435
+ $ghost.attr('id', '#tabs_' + this.name + '_tab_ghost')
436
+ this.last.moving = {
437
+ index: tabIndex,
438
+ indexFrom: tabIndex,
439
+ $tab: $tab,
440
+ $ghost: $ghost,
441
+ divX: 0,
442
+ left: $tab.get(0).getBoundingClientRect().left,
443
+ parentX: query(this.box).get(0).getBoundingClientRect().left,
444
+ x: event.pageX,
445
+ opacity: $tab.css('opacity')
446
+ }
447
+
448
+ query(document)
449
+ .off('.w2uiTabReorder')
450
+ .on('mousemove.w2uiTabReorder', function (event) {
451
+ if (!self.last.reordering) {
452
+ // event before
453
+ edata = self.trigger('reorder', { target: self.tabs[tabIndex].id, indexFrom: tabIndex, tab: self.tabs[tabIndex] })
454
+ if (edata.isCancelled === true) return
455
+
456
+ w2tooltip.hide(this.name + '_tooltip')
457
+ self.last.reordering = true
458
+ $ghost.addClass('moving')
459
+ $ghost.css({
460
+ 'pointer-events': 'none',
461
+ 'position': 'absolute',
462
+ 'left': $tab.get(0).getBoundingClientRect().left
463
+ })
464
+ $tab.css('opacity', 0)
465
+ query(self.box).find('.w2ui-scroll-wrapper').append($ghost.get(0))
466
+ query(self.box).find('.w2ui-tab-close').hide()
467
+ }
468
+ self.last.moving.divX = event.pageX - self.last.moving.x
469
+ $ghost.css('left', (self.last.moving.left - self.last.moving.parentX + self.last.moving.divX) + 'px')
470
+ self.dragMove(event)
471
+ })
472
+ .on('mouseup.w2uiTabReorder', function () {
473
+ query(document).off('.w2uiTabReorder')
474
+ $ghost.css({
475
+ 'transition': '0.1s',
476
+ 'left': self.last.moving.$tab.get(0).getBoundingClientRect().left - self.last.moving.parentX
477
+ })
478
+ query(self.box).find('.w2ui-tab-close').show()
479
+ $ghost.remove()
480
+ $tab.css({ opacity: self.last.moving.opacity })
481
+ // self.render()
482
+ if (self.last.reordering) {
483
+ edata.finish({ indexTo: self.last.moving.index })
484
+ }
485
+ self.last.reordering = false
486
+ })
487
+ }
488
+
489
+ scroll(direction, instant) {
490
+ return new Promise((resolve, reject) => {
491
+ let scrollBox = query(this.box).find('.w2ui-scroll-wrapper')
492
+ let scrollLeft = scrollBox.get(0).scrollLeft
493
+ let right = scrollBox.find('.w2ui-tabs-right').get(0)
494
+ let width1 = scrollBox.parent().get(0).getBoundingClientRect().width
495
+ let width2 = scrollLeft + parseInt(right.offsetLeft) + parseInt(right.clientWidth )
496
+
497
+ switch (direction) {
498
+ case 'left': {
499
+ let scroll = scrollLeft - width1 + 50 // 35 is width of both button
500
+ if (scroll <= 0) scroll = 0
501
+ scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' })
502
+ break
503
+ }
504
+ case 'right': {
505
+ let scroll = scrollLeft + width1 - 50 // 35 is width of both button
506
+ if (scroll >= width2 - width1) scroll = width2 - width1
507
+ scrollBox.get(0).scrollTo({ top: 0, left: scroll, behavior: instant ? 'atuo' : 'smooth' })
508
+ break
509
+ }
510
+ }
511
+ setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 350)
512
+ })
513
+ }
514
+
515
+ scrollIntoView(id, instant) {
516
+ return new Promise((resolve, reject) => {
517
+ if (id == null) id = this.active
518
+ let tab = this.get(id)
519
+ if (tab == null) return
520
+ let tabEl = query(this.box).find('#tabs_' + this.name + '_tab_' + w2utils.escapeId(id)).get(0)
521
+ tabEl.scrollIntoView({ block: 'start', inline: 'center', behavior: instant ? 'atuo' : 'smooth' })
522
+ setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 500)
523
+ })
524
+ }
525
+
526
+ resize() {
527
+ let time = Date.now()
528
+ if (this.box == null) return
529
+ // event before
530
+ let edata = this.trigger('resize', { target: this.name })
531
+ if (edata.isCancelled === true) return
532
+
533
+ // show hide overflow buttons
534
+ if (this.box != null) {
535
+ let box = query(this.box)
536
+ box.find('.w2ui-scroll-left, .w2ui-scroll-right').hide()
537
+ let scrollBox = box.find('.w2ui-scroll-wrapper').get(0)
538
+ let $right = box.find('.w2ui-tabs-right')
539
+ let boxWidth = box.get(0).getBoundingClientRect().width
540
+ let itemsWidth = ($right.length > 0 ? $right[0].offsetLeft + $right[0].clientWidth : 0)
541
+ if (boxWidth < itemsWidth) {
542
+ // we have overflown content
543
+ if (scrollBox.scrollLeft > 0) {
544
+ box.find('.w2ui-scroll-left').show()
545
+ }
546
+ if (boxWidth < itemsWidth - scrollBox.scrollLeft) {
547
+ box.find('.w2ui-scroll-right').show()
548
+ }
549
+ }
550
+ }
551
+ // event after
552
+ edata.finish()
553
+ return Date.now() - time
554
+ }
555
+
556
+ destroy() {
557
+ // event before
558
+ let edata = this.trigger('destroy', { target: this.name })
559
+ if (edata.isCancelled === true) return
560
+ // clean up
561
+ if (query(this.box).find('#tabs_'+ this.name + '_right').length > 0) {
562
+ this.unmount()
563
+ }
564
+ delete w2ui[this.name]
565
+ // event after
566
+ edata.finish()
567
+ }
568
+
569
+ unmount() {
570
+ super.unmount()
571
+ this.last.observeResize?.disconnect()
572
+ }
573
+
574
+ // ===================================================
575
+ // -- Internal Event Handlers
576
+
577
+ click(id, event) {
578
+ if (event && query(event.target).hasClass('w2ui-tab-close')) {
579
+ // do not consider click on close button as tab click
580
+ return
581
+ }
582
+ let tab = this.get(id)
583
+ if (tab == null || tab.disabled || this.last.reordering) return false
584
+ // event before
585
+ let edata = this.trigger('click', { target: id, tab: tab, object: tab, originalEvent: event })
586
+ if (edata.isCancelled === true) return
587
+ // default action
588
+ query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.active)).removeClass('active')
589
+ this.active = tab.id
590
+ query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(this.active)).addClass('active')
591
+ // route processing
592
+ if (typeof tab.route == 'string') {
593
+ let route = tab.route !== '' ? String('/'+ tab.route).replace(/\/{2,}/g, '/') : ''
594
+ let info = w2utils.parseRoute(route)
595
+ if (info.keys.length > 0) {
596
+ for (let k = 0; k < info.keys.length; k++) {
597
+ if (this.routeData[info.keys[k].name] == null) continue
598
+ route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name])
599
+ }
600
+ }
601
+ setTimeout(() => { window.location.hash = route }, 1)
602
+ }
603
+ // event after
604
+ edata.finish()
605
+ }
606
+
607
+ clickClose(id, event) {
608
+ let tab = this.get(id)
609
+ if (tab == null || tab.disabled) return false
610
+ // event before
611
+ let edata = this.trigger('close', { target: id, object: tab, tab, originalEvent: event })
612
+ if (edata.isCancelled === true) return
613
+ this.animateClose(id).then(() => {
614
+ this.remove(id)
615
+ edata.finish()
616
+ this.refresh()
617
+ })
618
+ event?.stopPropagation()
619
+ }
620
+
621
+ animateClose(id) {
622
+ return new Promise((resolve, reject) => {
623
+ let $tab = query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id))
624
+ let width = parseInt($tab.get(0).clientWidth || 0)
625
+ let anim = `<div class="tab-animate-close" style="display: inline-block; flex-shrink: 0; width: ${width}px; transition: width 0.25s"></div>`
626
+ let $anim = $tab.replace(anim)
627
+ setTimeout(() => { $anim.css({ width: '0px' }) }, 1)
628
+ setTimeout(() => {
629
+ $anim.remove()
630
+ this.resize()
631
+
632
+ resolve()
633
+ }, 500)
634
+ })
635
+ }
636
+
637
+ animateInsert(id, tab) {
638
+ return new Promise((resolve, reject) => {
639
+ let $before = query(this.box).find('#tabs_'+ this.name +'_tab_'+ w2utils.escapeId(id))
640
+ let $tab = query.html(this.getTabHTML(tab.id))
641
+ if ($before.length == 0) {
642
+ $before = query(this.box).find('#tabs_tabs_right')
643
+ $before.before($tab)
644
+ this.resize()
645
+ } else {
646
+ $tab.css({ opacity: 0 })
647
+ // first insert tab on the right to get its proper dimentions
648
+ query(this.box).find('#tabs_tabs_right').before($tab.get(0))
649
+ let $tmp = query(this.box).find('#' + $tab.attr('id'))
650
+ let width = $tmp.get(0)?.clientWidth ?? 0
651
+ // insert animation div
652
+ let $anim = query.html('<div class="tab-animate-insert" style="flex-shrink: 0; width: 0; transition: width 0.25s"></div>')
653
+ $before.before($anim)
654
+ // hide tab and move it in the right position
655
+ $tab.hide()
656
+ $anim.before($tab[0])
657
+ setTimeout(() => { $anim.css({ width: width + 'px' }) }, 1)
658
+ setTimeout(() => {
659
+ $anim.remove()
660
+ $tab.css({ opacity: 1 }).show()
661
+ this.refresh(tab.id)
662
+ this.resize()
663
+ resolve()
664
+ }, 500)
665
+ }
666
+ })
667
+ }
668
+ }
669
+ export { w2tabs }