@kaspernj/api-maker 1.0.445 → 1.0.446

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaspernj/api-maker",
3
- "version": "1.0.445",
3
+ "version": "1.0.446",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "main": "index.js",
@@ -35,6 +35,7 @@
35
35
  "object-to-formdata": ">= 4.1.0",
36
36
  "on-location-changed": ">= 1.0.13",
37
37
  "qs": ">= 6.9.3",
38
+ "react-native-draglist": "^3.8.0",
38
39
  "react-native-vector-icons": "^10.2.0",
39
40
  "replaceall": ">= 0.1.6",
40
41
  "set-state-compare": ">= 1.0.53",
@@ -0,0 +1,137 @@
1
+ import {digg} from "diggerize"
2
+ import EventEmitter from "events"
3
+
4
+ export default class DraggableSortController {
5
+ constructor({data, events, keyExtractor}) {
6
+ this.data = data
7
+ this.currentOrder = [...data]
8
+ this.events = events || new EventEmitter()
9
+ this.keyExtractor = keyExtractor
10
+
11
+ this.draggedItem = null
12
+ this.draggedItemData = null
13
+ this.draggedItemNewPosition = null
14
+
15
+ this.draggedOverItem = null
16
+ this.draggedOverItemData = null
17
+
18
+ this.itemData = {}
19
+
20
+ for (const itemIndex in data) {
21
+ this.itemData[itemIndex] = {
22
+ index: itemIndex,
23
+ item: data[itemIndex],
24
+ position: itemIndex
25
+ }
26
+ }
27
+ }
28
+
29
+ getEvents = () => this.events
30
+ getItemDataForItem = (item) => this.getItemDataForIndex(this.data.indexOf(item))
31
+ getItemDataForKey = (key) => this.itemData.find((itemDataI) => digg(itemDataI, "key") == key)
32
+ getItemDataForIndex = (index) => digg(this, "itemData", index)
33
+
34
+ onDragStart = ({item, itemIndex}) => {
35
+ if (item) {
36
+ this.draggedItem = item
37
+ this.draggedItemData = this.getItemDataForIndex(itemIndex)
38
+ this.draggedItemIndex = itemIndex
39
+ this.draggedItemPosition = this.draggedItemData.position
40
+ this.events.emit("onDragStart", {item, itemData: this.draggedItemData})
41
+ }
42
+ }
43
+
44
+ onDragEnd = () => {
45
+ const itemData = this.draggedItemData
46
+ const fromIndex = itemData.index
47
+ const fromPosition = digg(this, "draggedItemPosition")
48
+ const toPosition = this.draggedItemNewPosition
49
+ const fromItem = this.draggedItem
50
+ const toItem = this.draggedOverItem
51
+ const callbackArgs = {item: itemData.item, itemData, fromIndex, fromItem, fromPosition, toItem, toPosition}
52
+
53
+ this.draggedItemData.events.emit("resetPosition", {
54
+ callback: () => {
55
+ this.events.emit("onDragEndAnimation", callbackArgs)
56
+ }
57
+ })
58
+
59
+ this.draggedItem = null
60
+ this.draggedItemData = null
61
+ this.draggedItemNewPosition = null
62
+
63
+ this.draggedOverItem = null
64
+ this.draggedOverItemData = null
65
+
66
+ this.events.emit("onDragEnd", callbackArgs)
67
+ }
68
+
69
+ onItemLayout = ({events, index, item, layout}) => {
70
+ if (!(index in this.itemData)) throw new Error(`Item not found for index ${index}`)
71
+
72
+ const itemData = this.itemData[index]
73
+
74
+ itemData.layout = layout
75
+
76
+ if (!itemData.initialLayout) {
77
+ itemData.baseX = layout.x
78
+ itemData.events = events
79
+ itemData.initialLayout = layout
80
+ itemData.key = this.keyExtractor(item)
81
+ }
82
+ }
83
+
84
+ onMove = ({gestate}) => {
85
+ // Send move-event to the item being dragged so it will actually move around
86
+ this.draggedItemData?.events?.emit("move", {gestate})
87
+
88
+ const moveX = gestate.dx + this.initialDragPosition.x
89
+
90
+ for (const itemIndex in this.itemData) {
91
+ const itemData = this.getItemDataForIndex(itemIndex)
92
+ const baseX = digg(itemData, "baseX")
93
+ let smallestWidth = this.draggedItemData.layout.width
94
+
95
+ if (itemData.layout.width < smallestWidth) smallestWidth = itemData.layout.width
96
+
97
+ if (moveX > baseX && moveX < (baseX + smallestWidth) && itemIndex != this.draggedItemData.index) {
98
+ this.draggedOverItem = itemData.item
99
+ this.draggedOverItemData = itemData
100
+
101
+ const positionOfDraggedItem = this.currentOrder.indexOf(this.draggedItemData.item)
102
+ const positionOfOverItem = this.currentOrder.indexOf(this.draggedOverItemData.item)
103
+
104
+ this.currentOrder[positionOfDraggedItem] = this.draggedOverItemData.item
105
+ this.currentOrder[positionOfOverItem] = this.draggedItemData.item
106
+
107
+ this.draggedItemData.position = positionOfOverItem
108
+ this.draggedOverItemData.position = positionOfDraggedItem
109
+
110
+ this.updatePositionOfItems()
111
+ this.draggedItemNewPosition = positionOfOverItem
112
+ }
113
+ }
114
+ }
115
+
116
+ setInitialDragPosition = (initialDragPosition) => {
117
+ this.initialDragPosition = initialDragPosition
118
+ }
119
+
120
+ updatePositionOfItems() {
121
+ let currentPosition = 0
122
+
123
+ for (const item of this.currentOrder) {
124
+ const itemData = this.getItemDataForItem(item)
125
+
126
+ if (digg(itemData, "index") != digg(this, "draggedItemData", "index")) { // Dont animate dragged element to a new position because it is currently being dragged
127
+ itemData.events.emit("moveToPosition", {
128
+ x: currentPosition,
129
+ y: 0
130
+ })
131
+ }
132
+
133
+ itemData.baseX = currentPosition
134
+ currentPosition += digg(itemData, "layout", "width")
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,108 @@
1
+ import {useMemo} from "react"
2
+ import {Animated, PanResponder} from "react-native"
3
+ import {shapeComponent, ShapeComponent} from "set-state-compare/src/shape-component.js"
4
+ import Controller from "./controller.mjs"
5
+ import DraggableSortItem from "./item"
6
+ import EventEmitter from "events"
7
+ import memo from "set-state-compare/src/memo"
8
+ import PropTypes from "prop-types"
9
+ import propTypesExact from "prop-types-exact"
10
+ import useEventEmitter from "../use-event-emitter.mjs"
11
+
12
+ export default memo(shapeComponent(class DraggableSort extends ShapeComponent {
13
+ static defaultProps = {
14
+ horizontal: false
15
+ }
16
+
17
+ static propTypes = propTypesExact({
18
+ cacheKeyExtractor: PropTypes.func,
19
+ data: PropTypes.array.isRequired,
20
+ dataSet: PropTypes.object,
21
+ events: PropTypes.instanceOf(EventEmitter),
22
+ horizontal: PropTypes.bool.isRequired,
23
+ keyExtractor: PropTypes.func.isRequired,
24
+ onDragItemEnd: PropTypes.func,
25
+ onDragItemStart: PropTypes.func,
26
+ onItemMoved: PropTypes.func,
27
+ onReordered: PropTypes.func.isRequired,
28
+ renderItem: PropTypes.func.isRequired
29
+ })
30
+
31
+ setup() {
32
+ const {data, keyExtractor} = this.p
33
+ const {events} = this.props
34
+
35
+ this.controller = useMemo(() => new Controller({data, events, keyExtractor}), [])
36
+ this.panResponder = useMemo(
37
+ () => PanResponder.create({
38
+ onStartShouldSetPanResponder: (e) => {
39
+ const initialDragPosition = {x: e.nativeEvent.locationX, y: e.nativeEvent.locationY}
40
+
41
+ this.controller.setInitialDragPosition(initialDragPosition)
42
+
43
+ if (this.controller.draggedItemData) {
44
+ return true
45
+ }
46
+ },
47
+ onPanResponderMove: (e, gestate) => {
48
+ this.tt.controller.onMove({gestate})
49
+ },
50
+ onPanResponderRelease: (e, gestate) => {
51
+ if (this.controller.draggedItem) {
52
+ this.tt.controller.onDragEnd()
53
+ }
54
+ }
55
+ }),
56
+ []
57
+ )
58
+
59
+ useEventEmitter(this.controller.getEvents(), "onDragStart", this.tt.onDragItemStart)
60
+ useEventEmitter(this.controller.getEvents(), "onDragEnd", this.tt.onDragItemEnd)
61
+ }
62
+
63
+ render() {
64
+ const {data, horizontal, keyExtractor, renderItem} = this.p
65
+ const {cacheKeyExtractor, dataSet} = this.props
66
+ const actualDataSet = useMemo(
67
+ () => Object.assign(
68
+ {
69
+ component: "draggable-sort"
70
+ },
71
+ dataSet
72
+ ),
73
+ [dataSet]
74
+ )
75
+
76
+ return (
77
+ <Animated.View dataSet={actualDataSet} style={{flexDirection: horizontal ? "row" : "column"}} {...this.tt.panResponder.panHandlers}>
78
+ {data.map((item, itemIndex) =>
79
+ <DraggableSortItem
80
+ cacheKey={cacheKeyExtractor ? cacheKeyExtractor(item) : undefined}
81
+ controller={this.tt.controller}
82
+ item={item}
83
+ itemIndex={itemIndex}
84
+ key={keyExtractor(item)}
85
+ onItemMoved={this.props.onItemMoved}
86
+ renderItem={renderItem}
87
+ />
88
+ )}
89
+ </Animated.View>
90
+ )
91
+ }
92
+
93
+ onDragItemStart = ({itemData}) => {
94
+ if (this.props.onDragItemStart) {
95
+ this.p.onDragItemStart({itemData})
96
+ }
97
+ }
98
+
99
+ onDragItemEnd = (args) => {
100
+ if (args.toPosition !== null) {
101
+ this.p.onReordered(args)
102
+ }
103
+
104
+ if (this.props.onDragItemEnd) {
105
+ this.p.onDragItemEnd(args)
106
+ }
107
+ }
108
+ }))
@@ -0,0 +1,174 @@
1
+ import {useMemo} from "react"
2
+ import {Animated, Easing, PanResponder} from "react-native"
3
+ import {shapeComponent, ShapeComponent} from "set-state-compare/src/shape-component.js"
4
+ import EventEmitter from "events"
5
+ import memo from "set-state-compare/src/memo"
6
+ import PropTypes from "prop-types"
7
+ import propTypesExact from "prop-types-exact"
8
+ import useEventEmitter from "../use-event-emitter.mjs"
9
+
10
+ export default memo(shapeComponent(class DraggableSortItem extends ShapeComponent {
11
+ static propTypes = propTypesExact({
12
+ cacheKey: PropTypes.string,
13
+ controller: PropTypes.object.isRequired,
14
+ item: PropTypes.any.isRequired,
15
+ itemIndex: PropTypes.number.isRequired,
16
+ onItemMoved: PropTypes.func,
17
+ renderItem: PropTypes.func.isRequired
18
+ })
19
+
20
+ initialLayout = null
21
+
22
+ setup() {
23
+ this.useStates({
24
+ active: false,
25
+ dragging: false
26
+ })
27
+
28
+ this.events = useMemo(() => new EventEmitter(), [])
29
+ this.position = useMemo(() => new Animated.ValueXY(), [])
30
+ this.panResponder = useMemo(
31
+ () => PanResponder.create({
32
+ onStartShouldSetPanResponder: (e, ) => {
33
+ this.setState({dragging: true})
34
+ this.p.controller.onDragStart({item: this.p.item, itemIndex: this.p.itemIndex})
35
+
36
+ return false
37
+ }
38
+ }),
39
+ []
40
+ )
41
+
42
+ useEventEmitter(this.p.controller.getEvents(), "onDragStart", this.tt.onDragStart)
43
+ useEventEmitter(this.p.controller.getEvents(), "onDragEndAnimation", this.tt.onDragEndAnimation)
44
+ useEventEmitter(this.tt.events, "move", this.tt.onMove)
45
+ useEventEmitter(this.tt.events, "moveToPosition", this.tt.onMoveToPosition)
46
+ useEventEmitter(this.tt.events, "resetPosition", this.tt.onResetPosition)
47
+ }
48
+
49
+ render() {
50
+ const {item, renderItem} = this.p
51
+ const {active} = this.s
52
+ const style = useMemo(
53
+ () => {
54
+ const style = {
55
+ transform: this.tt.position.getTranslateTransform()
56
+ }
57
+
58
+ if (active) {
59
+ style.backgroundColor = "#fff"
60
+ style.elevation = 2
61
+ style.zIndex = 99999
62
+ }
63
+
64
+ return style
65
+ },
66
+ [active]
67
+ )
68
+
69
+ return (
70
+ <Animated.View dataSet={{component: "draggable-sort/item"}} onLayout={this.tt.onLayout} style={style}>
71
+ {renderItem({isActive: active, item, touchProps: this.tt.panResponder.panHandlers})}
72
+ </Animated.View>
73
+ )
74
+ }
75
+
76
+ onDragStart = ({itemData}) => {
77
+ const newState = {dragging: true}
78
+
79
+ if (itemData.index == this.p.itemIndex) {
80
+ newState.active = true
81
+ this.baseXAtStartedDragging = this.getBaseX()
82
+ }
83
+
84
+ this.setState(newState)
85
+ }
86
+
87
+ onDragEndAnimation = () => this.setState({active: false, dragging: false})
88
+
89
+ onLayout = (e) => {
90
+ const {controller, item, itemIndex} = this.p
91
+
92
+ controller.onItemLayout({events: this.tt.events, index: itemIndex, item, layout: e.nativeEvent.layout})
93
+
94
+ if (!this.tt.initialLayout) {
95
+ this.initialLayout = e.nativeEvent.layout
96
+ }
97
+ }
98
+
99
+ onMove = ({gestate}) => {
100
+ const x = gestate.dx + this.tt.baseXAtStartedDragging - this.tt.initialLayout.x
101
+ const y = this.tt.initialLayout.y
102
+
103
+ this.tt.position.setValue({x, y})
104
+
105
+ if (this.props.onItemMoved) {
106
+ this.p.onItemMoved({itemIndex: this.p.itemIndex, x, y})
107
+ }
108
+ }
109
+
110
+ onMoveToPosition = ({x, y}) => {
111
+ const calculatedXFromStartingPosition = x - this.tt.initialLayout.x
112
+ const animationArgs = {
113
+ duration: 200,
114
+ easing: Easing.inOut(Easing.linear),
115
+ toValue: {
116
+ x: calculatedXFromStartingPosition,
117
+ y
118
+ },
119
+ useNativeDriver: true,
120
+ }
121
+ const animationEventArgs = {animationArgs, animationType: "moveToPosition", item: this.p.item}
122
+
123
+ this.p.controller.events.emit("onAnimationStart", animationEventArgs)
124
+
125
+ Animated
126
+ .timing(this.tt.position, animationArgs)
127
+ .start(() => {
128
+ this.p.controller.events.emit("onAnimationEnd", animationEventArgs)
129
+ })
130
+
131
+ if (this.props.onItemMoved) {
132
+ this.p.onItemMoved({
133
+ animationArgs,
134
+ itemIndex: this.p.itemIndex,
135
+ x: calculatedXFromStartingPosition,
136
+ y
137
+ })
138
+ }
139
+ }
140
+
141
+ getBaseX = () => this.p.controller.getItemDataForIndex(this.p.itemIndex).baseX
142
+
143
+ onResetPosition = (args) => {
144
+ const baseX = this.getBaseX() - this.tt.initialLayout.x
145
+ const animationArgs = {
146
+ duration: 200,
147
+ easing: Easing.inOut(Easing.linear),
148
+ toValue: {
149
+ x: baseX,
150
+ y: 0
151
+ },
152
+ useNativeDriver: true,
153
+ }
154
+ const animationEventArgs = {animationArgs, animationType: "resetPosition", item: this.p.item}
155
+
156
+ this.p.controller.events.emit("onAnimationStart", animationEventArgs)
157
+
158
+ Animated
159
+ .timing(this.tt.position, animationArgs)
160
+ .start(() => {
161
+ this.p.controller.events.emit("onAnimationEnd", animationEventArgs)
162
+ if (args?.callback) args.callback()
163
+ })
164
+
165
+ if (this.props.onItemMoved) {
166
+ this.p.onItemMoved({
167
+ animationArgs,
168
+ itemIndex: this.p.itemIndex,
169
+ x: baseX,
170
+ y: 0
171
+ })
172
+ }
173
+ }
174
+ }))
@@ -1,12 +1,18 @@
1
1
  import BaseComponent from "../../base-component"
2
2
  import memo from "set-state-compare/src/memo"
3
3
  import {shapeComponent} from "set-state-compare/src/shape-component"
4
- import {View} from "react-native"
4
+ import {Animated, View} from "react-native"
5
5
 
6
6
  export default memo(shapeComponent(class SharedTableColumn extends BaseComponent {
7
7
  render() {
8
+ const {dataSet, ...restProps} = this.props
9
+ const actualDataSet = Object.assign(
10
+ {component: "api-maker/table/components/column"},
11
+ dataSet
12
+ )
13
+
8
14
  return (
9
- <View {...this.props} />
15
+ <Animated.View dataSet={actualDataSet} {...restProps} />
10
16
  )
11
17
  }
12
18
  }))
@@ -2,7 +2,7 @@ import BaseComponent from "../../base-component"
2
2
  import classNames from "classnames"
3
3
  import memo from "set-state-compare/src/memo"
4
4
  import {shapeComponent} from "set-state-compare/src/shape-component"
5
- import {View} from "react-native"
5
+ import {Animated} from "react-native"
6
6
 
7
7
  export default memo(shapeComponent(class SharedTableHeader extends BaseComponent {
8
8
  render() {
@@ -14,7 +14,7 @@ export default memo(shapeComponent(class SharedTableHeader extends BaseComponent
14
14
  )
15
15
 
16
16
  return (
17
- <View dataSet={actualDataSet} {...restProps} />
17
+ <Animated.View dataSet={actualDataSet} {...restProps} />
18
18
  )
19
19
  }
20
20
  }))
@@ -1,23 +1,27 @@
1
+ import {useMemo} from "react"
1
2
  import BaseComponent from "../base-component"
2
3
  import classNames from "classnames"
4
+ import FontAwesomeIcon from "react-native-vector-icons/FontAwesome"
3
5
  import Header from "./components/header"
4
6
  import HeaderColumnContent from "./header-column-content"
5
7
  import memo from "set-state-compare/src/memo"
6
- import {Platform, Pressable} from "react-native"
8
+ import {Animated, PanResponder} from "react-native"
7
9
  import PropTypes from "prop-types"
8
10
  import propTypesExact from "prop-types-exact"
9
11
  import {shapeComponent} from "set-state-compare/src/shape-component"
10
12
  import useBreakpoint from "../use-breakpoint"
11
- import useEventListener from "../use-event-listener.mjs"
12
13
  import Widths from "./widths"
13
14
 
14
15
  export default memo(shapeComponent(class ApiMakerTableHeaderColumn extends BaseComponent {
15
16
  static propTypes = propTypesExact({
17
+ active: PropTypes.bool.isRequired,
18
+ animatedWidth: PropTypes.instanceOf(Animated.Value).isRequired,
19
+ animatedZIndex: PropTypes.instanceOf(Animated.Value).isRequired,
16
20
  column: PropTypes.object.isRequired,
17
21
  resizing: PropTypes.bool.isRequired,
18
22
  table: PropTypes.object.isRequired,
19
23
  tableSettingColumn: PropTypes.object.isRequired,
20
- width: PropTypes.number.isRequired,
24
+ touchProps: PropTypes.object.isRequired,
21
25
  widths: PropTypes.instanceOf(Widths).isRequired
22
26
  })
23
27
 
@@ -25,29 +29,64 @@ export default memo(shapeComponent(class ApiMakerTableHeaderColumn extends BaseC
25
29
  const {name: breakpoint, mdUp, smDown} = useBreakpoint()
26
30
 
27
31
  this.setInstance({breakpoint, mdUp, smDown})
28
-
29
- useEventListener(window, "mousemove", this.tt.onWindowMouseMove)
30
- useEventListener(window, "mouseup", this.tt.onWindowMouseUp)
31
-
32
32
  this.useStates({
33
- cursorX: undefined,
34
- originalWidth: undefined,
35
33
  resizing: false
36
34
  })
35
+
36
+ this.resizePanResponder = useMemo(
37
+ () => PanResponder.create({
38
+ onStartShouldSetPanResponder: (_e) => {
39
+ this.originalWidth = this.currentWidth
40
+ this.setState({resizing: true})
41
+ this.p.table.setState({resizing: true})
42
+
43
+ return true
44
+ },
45
+ onPanResponderMove: (_e, gestate) => {
46
+ const newWidth = this.tt.originalWidth + gestate.dx
47
+
48
+ this.p.widths.setWidthOfColumn({
49
+ identifier: this.p.tableSettingColumn.identifier(),
50
+ width: newWidth
51
+ })
52
+ },
53
+ onPanResponderRelease: this.tt.onResizeEnd,
54
+ onPanResponderTerminate: this.tt.onResizeEnd,
55
+ onPanResponderTerminationRequest: () => false // Don't let another PanResponder steal focus and stop resizing until release
56
+ }),
57
+ []
58
+ )
59
+ }
60
+
61
+ onResizeEnd = async () => {
62
+ this.p.table.setState({lastUpdate: new Date(), resizing: false})
63
+ this.setState({resizing: false})
64
+
65
+ const width = this.p.widths.getWidthOfColumn(this.p.tableSettingColumn.identifier())
66
+
67
+ await this.p.tableSettingColumn.update({width})
37
68
  }
38
69
 
39
70
  render() {
40
71
  const {mdUp} = this.tt
41
- const {column, resizing, table, tableSettingColumn, width} = this.p
72
+ const {active, animatedWidth, column, resizing, table, tableSettingColumn, touchProps} = this.p
42
73
  const {styleForHeader} = table.tt
43
74
  const headerProps = table.headerProps(column)
44
75
  const {style, ...restColumnProps} = headerProps
45
- const actualStyle = Object.assign(
46
- {
47
- cursor: resizing ? "col-resize" : undefined,
48
- width: mdUp ? width : "100%"
76
+ const actualStyle = useMemo(
77
+ () => {
78
+ const actualStyle = Object.assign(
79
+ {
80
+ cursor: resizing ? "col-resize" : undefined,
81
+ width: mdUp ? animatedWidth : "100%",
82
+ height: mdUp ? "100%" : undefined
83
+ },
84
+ style
85
+ )
86
+
87
+ return actualStyle
49
88
  },
50
- style
89
+ [active, animatedWidth, mdUp, resizing, style]
51
90
  )
52
91
 
53
92
  return (
@@ -60,11 +99,12 @@ export default memo(shapeComponent(class ApiMakerTableHeaderColumn extends BaseC
60
99
  style={styleForHeader({style: actualStyle})}
61
100
  {...restColumnProps}
62
101
  >
102
+ {mdUp &&
103
+ <FontAwesomeIcon name="bars" style={{marginRight: 3, fontSize: 12}} {...touchProps} />
104
+ }
63
105
  <HeaderColumnContent column={column} table={table} tableSettingColumn={tableSettingColumn} />
64
106
  {mdUp &&
65
- <Pressable
66
- onMouseDown={Platform.OS == "web" ? this.tt.onResizeMouseDown : undefined}
67
- onPressIn={this.tt.onResizePressIn}
107
+ <Animated.View
68
108
  style={{
69
109
  position: "absolute",
70
110
  top: 0,
@@ -74,6 +114,7 @@ export default memo(shapeComponent(class ApiMakerTableHeaderColumn extends BaseC
74
114
  cursor: "col-resize",
75
115
  zIndex: 9999
76
116
  }}
117
+ {...this.tt.resizePanResponder.panHandlers}
77
118
  />
78
119
  }
79
120
  </Header>
@@ -85,65 +126,4 @@ export default memo(shapeComponent(class ApiMakerTableHeaderColumn extends BaseC
85
126
 
86
127
  this.currentWidth = width
87
128
  }
88
-
89
- onResizeEnd = async () => {
90
- this.setState({cursorX: undefined, resizing: false})
91
- this.p.table.setState({resizing: false})
92
-
93
- const width = this.p.widths.getWidthOfColumn(this.p.tableSettingColumn.identifier())
94
-
95
- await this.p.tableSettingColumn.update({width})
96
- }
97
-
98
- // Otherwise text is selectable on web
99
- onResizeMouseDown = (e) => {
100
- e.preventDefault()
101
- e.stopPropagation()
102
-
103
- const originalWidth = this.currentWidth
104
- const cursorX = e.nativeEvent.pageX
105
-
106
- this.setState({
107
- cursorX,
108
- originalWidth,
109
- resizing: true
110
- })
111
- this.p.table.setState({resizing: true})
112
- }
113
-
114
- onResizePressIn = (e) => {
115
- e.preventDefault()
116
- e.stopPropagation()
117
-
118
- const originalWidth = this.currentWidth
119
- const cursorX = e.nativeEvent.pageX
120
-
121
- this.setState({
122
- cursorX,
123
- originalWidth,
124
- resizing: true
125
- })
126
- this.p.table.setState({resizing: true})
127
- }
128
-
129
- onWindowMouseMove = (e) => {
130
- const {cursorX, resizing, originalWidth} = this.s
131
-
132
- if (resizing) {
133
- const newCursorX = e.pageX
134
- const diffX = newCursorX - cursorX
135
- const newWidth = originalWidth + diffX
136
-
137
- this.p.widths.setWidthOfColumn({
138
- identifier: this.p.tableSettingColumn.identifier(),
139
- width: newWidth
140
- })
141
- }
142
- }
143
-
144
- onWindowMouseUp = () => {
145
- if (this.s.resizing) {
146
- this.onResizeEnd()
147
- }
148
- }
149
129
  }))
@@ -1,9 +1,10 @@
1
- import {View} from "react-native"
1
+ import {Animated, View} from "react-native"
2
2
  import BaseComponent from "../base-component"
3
3
  import classNames from "classnames"
4
4
  import Column from "./components/column"
5
5
  import ColumnContent from "./column-content"
6
6
  import columnIdentifier from "./column-identifier.mjs"
7
+ import EventEmitter from "events"
7
8
  import PropTypes from "prop-types"
8
9
  import propTypesExact from "prop-types-exact"
9
10
  import memo from "set-state-compare/src/memo"
@@ -13,18 +14,21 @@ import useBreakpoint from "../use-breakpoint"
13
14
 
14
15
  export default memo(shapeComponent(class ApiMakerTableModelColumn extends BaseComponent {
15
16
  static propTypes = propTypesExact({
17
+ animatedPosition: PropTypes.instanceOf(Animated.ValueXY).isRequired,
18
+ animatedWidth: PropTypes.instanceOf(Animated.Value).isRequired,
19
+ animatedZIndex: PropTypes.instanceOf(Animated.Value).isRequired,
16
20
  column: PropTypes.object.isRequired,
17
21
  columnIndex: PropTypes.number.isRequired,
18
22
  even: PropTypes.bool.isRequired,
23
+ events: PropTypes.instanceOf(EventEmitter).isRequired,
19
24
  model: PropTypes.object.isRequired,
20
25
  table: PropTypes.object.isRequired,
21
- tableSettingColumn: PropTypes.object.isRequired,
22
- width: PropTypes.number.isRequired
26
+ tableSettingColumn: PropTypes.object.isRequired
23
27
  })
24
28
 
25
29
  render() {
26
30
  const {mdUp} = useBreakpoint()
27
- const {column, columnIndex, even, model, table, width} = this.props
31
+ const {animatedWidth, animatedZIndex, column, columnIndex, even, model, table} = this.props
28
32
  const columnProps = table.columnProps(column)
29
33
  const {style, ...restColumnProps} = columnProps
30
34
  const actualStyle = Object.assign(
@@ -33,7 +37,9 @@ export default memo(shapeComponent(class ApiMakerTableModelColumn extends BaseCo
33
37
  columnIndex,
34
38
  even,
35
39
  style: {
36
- width: mdUp ? width : "100%"
40
+ zIndex: animatedZIndex,
41
+ transform: this.p.animatedPosition.getTranslateTransform(),
42
+ width: mdUp ? animatedWidth : "100%"
37
43
  }
38
44
  }),
39
45
  style
@@ -2,7 +2,7 @@ import {Pressable} from "react-native"
2
2
  import BaseComponent from "../base-component"
3
3
  import Column from "./components/column"
4
4
  import columnIdentifier from "./column-identifier.mjs"
5
- import columnVisible from "./column-visible.mjs"
5
+ import EventEmitter from "events"
6
6
  import FontAwesomeIcon from "react-native-vector-icons/FontAwesome"
7
7
  import * as inflection from "inflection"
8
8
  import modelCallbackArgs from "./model-callback-args.mjs"
@@ -19,11 +19,12 @@ const WorkerPluginsCheckbox = React.lazy(() => import("./worker-plugins-checkbox
19
19
  export default memo(shapeComponent(class ApiMakerBootStrapLiveTableModelRow extends BaseComponent {
20
20
  static propTypes = propTypesExact({
21
21
  cacheKey: PropTypes.string.isRequired,
22
+ columns: PropTypes.array,
22
23
  columnWidths: PropTypes.object.isRequired,
24
+ events: PropTypes.instanceOf(EventEmitter).isRequired,
23
25
  index: PropTypes.number.isRequired,
24
26
  model: PropTypes.object.isRequired,
25
27
  table: PropTypes.object.isRequired,
26
- preparedColumns: PropTypes.array,
27
28
  tableSettingFullCacheKey: PropTypes.string.isRequired
28
29
  })
29
30
 
@@ -85,18 +86,21 @@ export default memo(shapeComponent(class ApiMakerBootStrapLiveTableModelRow exte
85
86
  }
86
87
 
87
88
  columnsContentFromColumns(model, even) {
88
- const {table, preparedColumns} = this.p
89
+ const {columns, events, table} = this.p
89
90
 
90
- return preparedColumns?.map(({column, tableSettingColumn, width}, columnIndex) => columnVisible(column, tableSettingColumn) &&
91
+ return columns?.map(({animatedPosition, animatedWidth, animatedZIndex, column, tableSettingColumn}, columnIndex) =>
91
92
  <ModelColumn
93
+ animatedPosition={animatedPosition}
94
+ animatedWidth={animatedWidth}
95
+ animatedZIndex={animatedZIndex}
92
96
  column={column}
93
97
  columnIndex={columnIndex}
94
98
  even={even}
99
+ events={events}
95
100
  key={columnIdentifier(column)}
96
101
  model={model}
97
102
  table={table}
98
103
  tableSettingColumn={tableSettingColumn}
99
- width={width}
100
104
  />
101
105
  )
102
106
  }
@@ -87,6 +87,7 @@ export default memo(shapeComponent(class ColumnRow extends BaseComponent {
87
87
  const {table, tableSettingColumn} = this.p
88
88
 
89
89
  await tableSettingColumn.update({visible: this.checked})
90
- table.updateSettingsFullCacheKey()
90
+
91
+ table.events.emit("columnVisibilityUpdated", {tableSettingColumn})
91
92
  }
92
93
  }))
@@ -1,11 +1,14 @@
1
1
  import {digg, digs} from "diggerize"
2
- import {Pressable, StyleSheet, View} from "react-native"
2
+ import React, {createContext, useContext, useMemo, useRef} from "react"
3
+ import {Animated, Pressable, View} from "react-native"
3
4
  import BaseComponent from "../base-component"
4
5
  import Card from "../bootstrap/card"
5
6
  import classNames from "classnames"
6
7
  import Collection from "../collection"
7
8
  import columnVisible from "./column-visible.mjs"
8
9
  import debounce from "debounce"
10
+ import DraggableSort from "../draggable-sort/index.jsx"
11
+ import EventEmitter from "events"
9
12
  import Filters from "./filters"
10
13
  import FlatList from "./components/flat-list"
11
14
  import FontAwesomeIcon from "react-native-vector-icons/FontAwesome"
@@ -20,7 +23,6 @@ import ModelRow from "./model-row"
20
23
  import Paginate from "../bootstrap/paginate"
21
24
  import Params from "../params"
22
25
  import PropTypes from "prop-types"
23
- import React, {createContext, useContext, useMemo, useRef} from "react"
24
26
  import Row from "./components/row"
25
27
  import selectCalculator from "./select-calculator"
26
28
  import Select from "../inputs/select"
@@ -32,6 +34,7 @@ import uniqunize from "uniqunize"
32
34
  import useBreakpoint from "../use-breakpoint"
33
35
  import useCollection from "../use-collection"
34
36
  import useI18n from "i18n-on-steroids/src/use-i18n.mjs"
37
+ import useEventEmitter from "../use-event-emitter.mjs"
35
38
  import useModelEvent from "../use-model-event.js"
36
39
  import useQueryParams from "on-location-changed/src/use-query-params.js"
37
40
  import Widths from "./widths"
@@ -40,43 +43,55 @@ const paginationOptions = [30, 60, 90, ["All", "all"]]
40
43
  const WorkerPluginsCheckAllCheckbox = React.lazy(() => import("./worker-plugins-check-all-checkbox"))
41
44
  const TableContext = createContext()
42
45
 
43
- const ListHeaderComponent = memo(() => {
44
- const {mdUp} = useBreakpoint()
45
- const tableContextValue = useContext(TableContext)
46
- const table = tableContextValue.table
47
- const {collection, queryWithoutPagination, t} = table.tt
48
- const {query} = digs(collection, "query")
49
-
50
- return (
51
- <Row dataSet={{class: "api-maker/table/header-row"}} style={table.styleForRowHeader()}>
52
- {table.p.workplace && table.s.currentWorkplace &&
53
- <Header style={table.styleForHeader({style: {width: mdUp ? 41 : undefined}})}>
54
- <WorkerPluginsCheckAllCheckbox
55
- currentWorkplace={table.s.currentWorkplace}
56
- query={queryWithoutPagination}
57
- style={{marginHorizontal: "auto"}}
58
- />
59
- {!mdUp &&
60
- <Text style={{marginLeft: 3}}>
61
- {t(".select_all_found", {defaultValue: "Select all found"})}
62
- </Text>
63
- }
64
- </Header>
65
- }
66
- {!mdUp &&
67
- <Header style={table.styleForHeader({style: {}})}>
68
- <HeaderSelect preparedColumns={table.s.preparedColumns} query={query} table={table} />
69
- </Header>
70
- }
71
- {mdUp &&
72
- <>
73
- {table.headersContentFromColumns()}
74
- <Header style={table.styleForHeader({style: {}, type: "actions"})} />
75
- </>
76
- }
77
- </Row>
78
- )
79
- })
46
+ const ListHeaderComponent = memo(shapeComponent(class ListHeaderComponent extends BaseComponent {
47
+ setup() {
48
+ this.useStates({
49
+ lastUpdate: new Date()
50
+ })
51
+ }
52
+
53
+ render() {
54
+ const {mdUp} = useBreakpoint()
55
+ const tableContextValue = useContext(TableContext)
56
+ const table = tableContextValue.table
57
+ const {collection, events, queryWithoutPagination, t} = table.tt
58
+ const {query} = digs(collection, "query")
59
+
60
+ useEventEmitter(events, "columnVisibilityUpdated", this.tt.onColumnVisibilityUpdated)
61
+
62
+ return (
63
+ <Row dataSet={{class: "api-maker/table/header-row"}} style={table.styleForRowHeader()}>
64
+ {table.p.workplace && table.s.currentWorkplace &&
65
+ <Header style={table.styleForHeader({style: {width: mdUp ? 41 : undefined}})}>
66
+ <WorkerPluginsCheckAllCheckbox
67
+ currentWorkplace={table.s.currentWorkplace}
68
+ query={queryWithoutPagination}
69
+ style={{marginHorizontal: "auto"}}
70
+ />
71
+ {!mdUp &&
72
+ <Text style={{marginLeft: 3}}>
73
+ {t(".select_all_found", {defaultValue: "Select all found"})}
74
+ </Text>
75
+ }
76
+ </Header>
77
+ }
78
+ {!mdUp &&
79
+ <Header style={table.styleForHeader({style: {}})}>
80
+ <HeaderSelect preparedColumns={table.s.preparedColumns} query={query} table={table} />
81
+ </Header>
82
+ }
83
+ {mdUp &&
84
+ <>
85
+ {table.headersContentFromColumns()}
86
+ <Header style={table.styleForHeader({style: {}, type: "actions"})} />
87
+ </>
88
+ }
89
+ </Row>
90
+ )
91
+ }
92
+
93
+ onColumnVisibilityUpdated = () => this.setState({lastUpdate: new Date()})
94
+ }))
80
95
 
81
96
  export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
82
97
  static defaultProps = {
@@ -132,7 +147,9 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
132
147
  workplace: PropTypes.bool.isRequired
133
148
  }
134
149
 
135
- tableSetting = null
150
+ draggableSortEvents = new EventEmitter()
151
+ events = new EventEmitter()
152
+ tableSettings = null
136
153
 
137
154
  setup() {
138
155
  const {t} = useI18n({namespace: "js.api_maker.table"})
@@ -151,14 +168,15 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
151
168
 
152
169
  if (!queryName) queryName = collectionKey
153
170
 
154
- const columnsAsArray = this.columnsAsArray()
155
171
  const querySName = `${queryName}_s`
156
172
 
157
173
  this.useStates({
158
- columns: columnsAsArray,
174
+ columns: () => this.columnsAsArray(),
159
175
  currentWorkplace: undefined,
160
176
  currentWorkplaceCount: null,
161
177
  filterForm: null,
178
+ columnsToShow: null,
179
+ draggedColumn: null,
162
180
  identifier: () => this.props.identifier || `${collectionKey}-default`,
163
181
  lastUpdate: () => new Date(),
164
182
  preload: undefined,
@@ -181,9 +199,10 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
181
199
  () => ({
182
200
  cacheKey: this.s.tableSettingFullCacheKey,
183
201
  lastUpdate: this.s.lastUpdate,
202
+ resizing: this.s.resizing,
184
203
  table: this
185
204
  }),
186
- [this.s.lastUpdate, this.s.tableSettingFullCacheKey]
205
+ [this.s.lastUpdate, this.s.resizing, this.s.tableSettingFullCacheKey]
187
206
  )
188
207
 
189
208
  useMemo(() => {
@@ -195,7 +214,7 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
195
214
  }, [this.p.currentUser?.id()])
196
215
 
197
216
  useMemo(() => {
198
- if (!this.tt.tableSetting && this.s.width) {
217
+ if (!this.tt.tableSettings && this.s.width) {
199
218
  this.loadTableSetting()
200
219
  }
201
220
  }, [this.p.currentUser?.id(), this.s.width])
@@ -235,8 +254,16 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
235
254
  () => this.collection?.query?.clone()?.except("page"),
236
255
  [this.collection.query]
237
256
  )
257
+
258
+ useEventEmitter(this.tt.draggableSortEvents, "onDragStart", this.tt.onDragStart)
259
+ useEventEmitter(this.tt.draggableSortEvents, "onDragEndAnimation", this.tt.onDragEndAnimation)
260
+ useEventEmitter(this.tt.events, "columnVisibilityUpdated", this.tt.onColumnVisibilityUpdated)
238
261
  }
239
262
 
263
+ onColumnVisibilityUpdated = () => this.setState({columnsToShow: this.getColumnsToShow(this.s.columns), lastUpdate: new Date()})
264
+ onDragStart = ({item}) => item.animatedZIndex.setValue(9999)
265
+ onDragEndAnimation = ({item}) => item.animatedZIndex.setValue(0)
266
+
240
267
  async loadCurrentWorkplace() {
241
268
  const Workplace = modelClassRequire("Workplace")
242
269
  const result = await Workplace.current()
@@ -257,6 +284,12 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
257
284
  this.setState({currentWorkplaceCount})
258
285
  }
259
286
 
287
+ getColumnsToShow(columns) {
288
+ return columns
289
+ .filter(({column, tableSettingColumn}) => columnVisible(column, tableSettingColumn))
290
+ .sort((a, b) => a.tableSettingColumn.position() - b.tableSettingColumn.position())
291
+ }
292
+
260
293
  async loadTableSetting() {
261
294
  this.tableSettings = new TableSettings({table: this})
262
295
 
@@ -264,8 +297,11 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
264
297
  const {columns, preload} = this.tableSettings.preparedColumns(tableSetting)
265
298
  const {width} = this.s
266
299
  const widths = new Widths({columns, table: this, width})
300
+ const columnsToShow = this.getColumnsToShow(columns)
267
301
 
268
302
  this.setState({
303
+ columns,
304
+ columnsToShow,
269
305
  preparedColumns: columns,
270
306
  preload: this.mergedPreloads(preload),
271
307
  tableSetting,
@@ -450,7 +486,11 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
450
486
  <TableContext.Provider value={this.tt.tableContextValue}>
451
487
  <FlatList
452
488
  data={models}
453
- dataSet={{class: classNames("api-maker--table", className), cacheKey: this.s.tableSettingFullCacheKey}}
489
+ dataSet={{
490
+ class: classNames("api-maker--table", className),
491
+ cacheKey: this.s.tableSettingFullCacheKey,
492
+ lastUpdate: this.s.lastUpdate
493
+ }}
454
494
  extraData={this.s.lastUpdate}
455
495
  keyExtractor={this.tt.keyExtrator}
456
496
  ListHeaderComponent={ListHeaderComponent}
@@ -575,8 +615,6 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
575
615
  }
576
616
 
577
617
  renderItem = ({index, item: model}) => {
578
- const {preparedColumns, tableSettingFullCacheKey} = this.s
579
-
580
618
  if (!this.s.tableSettingLoaded) {
581
619
  return (
582
620
  <View>
@@ -590,13 +628,14 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
590
628
  return (
591
629
  <ModelRow
592
630
  cacheKey={model.cacheKey()}
631
+ columns={this.s.columnsToShow}
593
632
  columnWidths={this.columnWidths()}
633
+ events={this.tt.events}
594
634
  index={index}
595
635
  key={model.id()}
596
636
  model={model}
597
- preparedColumns={preparedColumns}
598
637
  table={this}
599
- tableSettingFullCacheKey={tableSettingFullCacheKey}
638
+ tableSettingFullCacheKey={this.s.tableSettingFullCacheKey}
600
639
  />
601
640
  )
602
641
  }
@@ -611,7 +650,7 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
611
650
 
612
651
  if (styleUI) {
613
652
  Object.assign(defaultStyle, {
614
- backgroundColor: even ? "#f5f5f5" : undefined
653
+ backgroundColor: even ? "#f5f5f5" : "#fff"
615
654
  })
616
655
  }
617
656
 
@@ -648,10 +687,6 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
648
687
  defaultStyle.borderRight = "1px solid #dbdbdb"
649
688
  }
650
689
 
651
- if (mdUp) {
652
-
653
- }
654
-
655
690
  const actualStyle = Object.assign(
656
691
  defaultStyle,
657
692
  style
@@ -798,17 +833,60 @@ export default memo(shapeComponent(class ApiMakerTable extends BaseComponent {
798
833
  return columnWidths
799
834
  }
800
835
 
801
- headersContentFromColumns = () => this.s.preparedColumns?.map(({column, tableSettingColumn, width}) => columnVisible(column, tableSettingColumn) &&
802
- <HeaderColumn
803
- column={column}
804
- key={tableSettingColumn.identifier()}
805
- resizing={this.s.resizing}
806
- table={this}
807
- tableSettingColumn={tableSettingColumn}
808
- width={width}
809
- widths={this.s.widths}
810
- />
811
- )
836
+ headersContentFromColumns = () => {
837
+ return (
838
+ <DraggableSort
839
+ data={this.s.columnsToShow}
840
+ events={this.tt.draggableSortEvents}
841
+ horizontal
842
+ keyExtractor={this.tt.dragListkeyExtractor}
843
+ onItemMoved={this.tt.onItemMoved}
844
+ onReordered={this.tt.onReordered}
845
+ renderItem={this.tt.dragListRenderItemContent}
846
+ />
847
+ )
848
+ }
849
+
850
+ dragListCacheKeyExtractor = (item) => `${item.tableSettingColumn.identifier()}-${this.s.resizing}`
851
+ dragListkeyExtractor = (item) => item.tableSettingColumn.identifier()
852
+
853
+ onItemMoved = ({animationArgs, itemIndex, x, y}) => {
854
+ const animatedPosition = digg(this, "s", "columnsToShow", itemIndex, "animatedPosition")
855
+
856
+ if (animationArgs) {
857
+ Animated.timing(animatedPosition, animationArgs).start()
858
+ } else {
859
+ animatedPosition.setValue({x, y})
860
+ }
861
+ }
862
+
863
+ onReordered = async ({fromItem, fromPosition, toItem, toPosition}) => {
864
+ if (fromPosition == toPosition) return // Only do requests and queries if changed
865
+
866
+ const TableSettingColumn = fromItem.tableSettingColumn.constructor
867
+ const toColumn = await TableSettingColumn.find(toItem.tableSettingColumn.id()) // Need to load latest position because ActsAsList might have changed it
868
+
869
+ await fromItem.tableSettingColumn.update({position: toColumn.position()})
870
+ }
871
+
872
+ dragListRenderItemContent = ({isActive, item, touchProps}) => {
873
+ const {animatedWidth, animatedZIndex, column, tableSettingColumn} = item
874
+
875
+ return (
876
+ <HeaderColumn
877
+ active={isActive}
878
+ animatedWidth={animatedWidth}
879
+ animatedZIndex={animatedZIndex}
880
+ column={column}
881
+ key={tableSettingColumn.identifier()}
882
+ resizing={this.s.resizing}
883
+ table={this}
884
+ tableSettingColumn={tableSettingColumn}
885
+ touchProps={touchProps}
886
+ widths={this.s.widths}
887
+ />
888
+ )
889
+ }
812
890
 
813
891
  headerClassNameForColumn(column) {
814
892
  const classNames = []
@@ -1,3 +1,4 @@
1
+ import {Animated, Easing} from "react-native"
1
2
  import {digg} from "diggerize"
2
3
 
3
4
  export default class TableWidths {
@@ -19,7 +20,15 @@ export default class TableWidths {
19
20
  const column = this.columns[columnIndex]
20
21
  const tableSettingColumn = column.tableSettingColumn
21
22
 
23
+ if (column.animatedPosition) throw new Error("Column already had an animated position")
24
+
25
+ column.animatedPosition = new Animated.ValueXY()
26
+ column.animatedZIndex = new Animated.Value(0)
27
+
22
28
  if (tableSettingColumn.hasWidth()) {
29
+ if (column.animatedWidth) throw new Error("Column already had an animated width")
30
+
31
+ column.animatedWidth = new Animated.Value(tableSettingColumn.width())
23
32
  column.width = tableSettingColumn.width()
24
33
 
25
34
  widthLeft -= tableSettingColumn.width()
@@ -44,6 +53,7 @@ export default class TableWidths {
44
53
 
45
54
  if (newWidth < 200) newWidth = 200
46
55
 
56
+ column.animatedWidth = new Animated.Value(newWidth)
47
57
  column.width = newWidth
48
58
 
49
59
  updateData << {
@@ -72,7 +82,6 @@ export default class TableWidths {
72
82
  if (!column) throw new Error(`No such column: ${identifier}`)
73
83
 
74
84
  column.width = width
75
-
76
- this.table.setState({lastUpdate: new Date()})
85
+ column.animatedWidth.setValue(width)
77
86
  }
78
87
  }