@kaspernj/api-maker 1.0.215 → 1.0.218

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 (52) hide show
  1. package/package.json +5 -4
  2. package/src/base-model.mjs +1 -1
  3. package/src/bootstrap/attribute-row/basic-style.scss +9 -0
  4. package/src/bootstrap/attribute-row/index.jsx +84 -0
  5. package/src/bootstrap/attribute-rows.jsx +27 -0
  6. package/src/bootstrap/card.jsx +135 -0
  7. package/src/bootstrap/checkbox.jsx +79 -0
  8. package/src/bootstrap/checkboxes.jsx +122 -0
  9. package/src/bootstrap/index.js +0 -0
  10. package/src/bootstrap/input.jsx +160 -0
  11. package/src/bootstrap/invalid-feedback.jsx +31 -0
  12. package/src/bootstrap/live-table/model-row.jsx +150 -0
  13. package/src/bootstrap/live-table.jsx +399 -0
  14. package/src/bootstrap/paginate.jsx +153 -0
  15. package/src/bootstrap/radio-buttons.jsx +87 -0
  16. package/src/bootstrap/select.jsx +110 -0
  17. package/src/bootstrap/sort-link.jsx +102 -0
  18. package/src/collection-loader.jsx +7 -8
  19. package/src/inputs/auto-submit.mjs +37 -0
  20. package/src/inputs/checkbox.jsx +97 -0
  21. package/src/inputs/checkboxes.jsx +113 -0
  22. package/src/inputs/id-for-component.mjs +15 -0
  23. package/src/inputs/input-wrapper.jsx +170 -0
  24. package/src/inputs/input.jsx +235 -0
  25. package/src/inputs/money.jsx +177 -0
  26. package/src/inputs/name-for-component.mjs +15 -0
  27. package/src/inputs/select.jsx +87 -0
  28. package/src/model-class-require.mjs +1 -1
  29. package/src/model-name.mjs +6 -2
  30. package/src/params.mjs +7 -0
  31. package/src/super-admin/index-page/index.jsx +44 -0
  32. package/src/super-admin/index.jsx +46 -0
  33. package/src/super-admin/layout/header/index.jsx +60 -0
  34. package/src/super-admin/layout/header/style.scss +124 -0
  35. package/src/super-admin/layout/index.jsx +156 -0
  36. package/src/super-admin/layout/menu/index.jsx +116 -0
  37. package/src/super-admin/layout/menu/menu-content.jsx +55 -0
  38. package/src/super-admin/layout/menu/menu-item/index.jsx +27 -0
  39. package/src/super-admin/layout/menu/menu-item/style.scss +30 -0
  40. package/src/super-admin/layout/menu/style.scss +103 -0
  41. package/src/super-admin/layout/no-access.jsx +16 -0
  42. package/src/super-admin/layout/style.scss +25 -0
  43. package/src/super-admin/show-page.jsx +9 -0
  44. package/src/table/column-identifier.mjs +23 -0
  45. package/src/table/column-visible.mjs +7 -0
  46. package/src/table/model-row.jsx +182 -0
  47. package/src/table/select-calculator.mjs +48 -0
  48. package/src/table/style.scss +72 -0
  49. package/src/table/table-settings.js +175 -0
  50. package/src/table/table.jsx +498 -0
  51. package/src/table/variables.scss +11 -0
  52. package/src/table/with-breakpoint.jsx +48 -0
@@ -0,0 +1,103 @@
1
+ @import "stylesheets/variables";
2
+
3
+ .components--admin--layout--menu {
4
+ position: fixed;
5
+ z-index: 9;
6
+ top: 0;
7
+ left: 0;
8
+ display: flex;
9
+ height: 100%;
10
+ flex-direction: column;
11
+ background: #1b1c1e;
12
+ color: #fff;
13
+
14
+ @media (max-width: $sm-to) {
15
+ width: 100%;
16
+ max-width: 250px;
17
+ max-height: 100vh;
18
+ overflow-y: auto;
19
+
20
+ &[data-triggered="false"] {
21
+ display: none;
22
+ }
23
+ }
24
+
25
+ @media (min-width: $md-from) {
26
+ width: 250px;
27
+ }
28
+
29
+ @media (min-width: $lg-from) {
30
+ width: 290px;
31
+ }
32
+
33
+ .menu-logo {
34
+ overflow: hidden;
35
+ width: 80%;
36
+ margin-top: 25px;
37
+ margin-right: auto;
38
+ margin-left: auto;
39
+ font-size: 42px;
40
+ text-align: center;
41
+ text-overflow: ellipsis;
42
+ white-space: nowrap;
43
+ }
44
+
45
+ .menu-logo-link {
46
+ &:link,
47
+ &:visited {
48
+ color: #dededf;
49
+ text-decoration: none;
50
+ }
51
+ }
52
+
53
+ .menu-items-center {
54
+ margin-top: 25px;
55
+ }
56
+
57
+ .menu-items-bottom {
58
+ margin-top: auto;
59
+ margin-bottom: 25px;
60
+ }
61
+
62
+ .menu-user-section {
63
+ display: flex;
64
+ align-items: center;
65
+ margin-right: 25px;
66
+ margin-bottom: 25px;
67
+ margin-left: 25px;
68
+ }
69
+
70
+ .menu-user-icon {
71
+ display: flex;
72
+ width: 44px;
73
+ min-width: 44px;
74
+ max-width: 44px;
75
+ height: 44px;
76
+ align-items: center;
77
+ justify-content: center;
78
+ background: #abbcd0;
79
+ border-radius: 50%;
80
+ }
81
+
82
+ .menu-user-name {
83
+ flex-shrink: 1;
84
+ margin-left: 8px;
85
+ }
86
+
87
+ .menu-user-name-container {
88
+ overflow: hidden;
89
+ max-width: 140px;
90
+ }
91
+
92
+ .menu-user-items {
93
+ position: relative;
94
+ margin-left: auto;
95
+ }
96
+
97
+ .menu-user-items-link {
98
+ &:link,
99
+ &:visited {
100
+ color: #dededf;
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,16 @@
1
+ class ComponentsAdminLayoutNoAccess extends BaseComponent {
2
+ render() {
3
+ const {currentUser} = digs(this.props, "currentUser")
4
+
5
+ return (
6
+ <div
7
+ className="components--admin--layout-no-access"
8
+ data-user-roles={currentUser?.userRoles()?.loaded()?.map((userRole) => userRole.role()?.identifier()).join(", ")}
9
+ >
10
+ {I18n.t("js.components.app_layout.no_access.you_dont_have_no_access_to_this_page")}
11
+ </div>
12
+ )
13
+ }
14
+ }
15
+
16
+ export default withCurrentUser(ComponentsAdminLayoutNoAccess)
@@ -0,0 +1,25 @@
1
+ @import "stylesheets/variables";
2
+
3
+ .components--admin--layout {
4
+ width: 100%;
5
+ min-height: 100vh;
6
+ background: #fff;
7
+ color: #000;
8
+
9
+ .app-layout-content-container {
10
+ min-height: 100vh;
11
+ background: #f7f7f7;
12
+
13
+ @media (max-width: $sm-to) {
14
+ padding: 130px 30px 30px;
15
+ }
16
+
17
+ @media (min-width: $md-from) {
18
+ padding: 130px 30px 30px 280px;
19
+ }
20
+
21
+ @media (min-width: $lg-from) {
22
+ padding: 130px 30px 30px 320px;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ export default class ApiMakerSuperAdminShowPage extends React.PureComponent {
2
+ render() {
3
+ return (
4
+ <div>
5
+ show page
6
+ </div>
7
+ )
8
+ }
9
+ }
@@ -0,0 +1,23 @@
1
+ export default function columnIdentifier(column) {
2
+ if ("identifier" in column) return column.identifier
3
+
4
+ const parts = []
5
+
6
+ if ("path" in column) {
7
+ for (const pathPart of column.path) {
8
+ parts.push(pathPart)
9
+ }
10
+ }
11
+
12
+ if ("attribute" in column) {
13
+ parts.push(`attribute-${column.attribute}`)
14
+ }
15
+
16
+ if ("sortKey" in column) {
17
+ parts.push(`sort-key-${column.sortKey}`)
18
+ }
19
+
20
+ if (parts.length == 0) throw new Error(`Couldn't figure out the identifier for that column: ${JSON.stringify(column)}`)
21
+
22
+ return parts.join("--")
23
+ }
@@ -0,0 +1,7 @@
1
+ export default function columnVisible(column, tableSettingColumn) {
2
+ if (tableSettingColumn.visible() !== null) return tableSettingColumn.visible()
3
+ if (!column) return false
4
+ if ("defaultVisible" in column) return column.defaultVisible
5
+
6
+ return true
7
+ }
@@ -0,0 +1,182 @@
1
+ import classNames from "classnames"
2
+ import columnIdentifier from "./column-identifier.mjs"
3
+ import columnVisible from "./column-visible.mjs"
4
+ import {digg, digs} from "diggerize"
5
+ import inflection from "inflection"
6
+ import Link from "../link"
7
+ import MoneyFormatter from "../money-formatter"
8
+ import PropTypes from "prop-types"
9
+
10
+ export default class ApiMakerBootStrapLiveTableModelRow extends React.PureComponent {
11
+ static propTypes = {
12
+ model: PropTypes.object.isRequired,
13
+ liveTable: PropTypes.object.isRequired,
14
+ preparedColumns: PropTypes.array
15
+ }
16
+
17
+ modelCallbackArgs = this._modelCallbackArgs()
18
+
19
+ render() {
20
+ const {model} = digs(this.props, "model")
21
+ const {modelClass} = digs(this.props.liveTable.props, "modelClass")
22
+ const {actionsContent, columnsContent, destroyEnabled, editModelPath, viewModelPath} = digg(this, "props", "liveTable", "props")
23
+ const {columns} = digg(this, "props", "liveTable", "shape")
24
+
25
+ let editPath, viewPath
26
+
27
+ if (editModelPath && model.can("edit")) editPath = editModelPath(this.modelCallbackArgs)
28
+ if (viewModelPath && model.can("show")) viewPath = viewModelPath(this.modelCallbackArgs)
29
+
30
+ const RowComponent = this.props.rowComponent
31
+ const ColumnComponent = this.props.columnComponent
32
+
33
+ return (
34
+ <RowComponent className={`live-table-row ${inflection.dasherize(modelClass.modelClassData().paramKey)}-row`} data-model-id={model.id()}>
35
+ {columns && this.columnsContentFromColumns(model)}
36
+ {!columns && columnsContent && columnsContent(this.modelCallbackArgs)}
37
+ <ColumnComponent className="actions-column">
38
+ {actionsContent && actionsContent(this.modelCallbackArgs)}
39
+ {viewPath &&
40
+ <Link className="view-button" to={viewPath}>
41
+ <i className="fa fa-search la la-search" />
42
+ </Link>
43
+ }
44
+ {editPath &&
45
+ <Link className="edit-button" to={editPath}>
46
+ <i className="fa fa-edit la la-edit" />
47
+ </Link>
48
+ }
49
+ {destroyEnabled && model.can("destroy") &&
50
+ <a className="destroy-button" href="#" onClick={this.onDestroyClicked}>
51
+ <i className="fa fa-trash la la-trash" />
52
+ </a>
53
+ }
54
+ </ColumnComponent>
55
+ </RowComponent>
56
+ )
57
+ }
58
+
59
+ columnClassNamesForColumn (column) {
60
+ const classNames = ["live-table-column"]
61
+
62
+ if (column.commonProps && column.commonProps.className) classNames.push(column.commonProps.className)
63
+ if (column.columnProps && column.columnProps.className) classNames.push(column.columnProps.className)
64
+
65
+ return classNames
66
+ }
67
+
68
+ columnsContentFromColumns (model) {
69
+ const {preparedColumns} = digs(this.props, "preparedColumns")
70
+ const ColumnComponent = this.props.columnComponent
71
+
72
+ return preparedColumns?.map(({column, tableSettingColumn}) => columnVisible(column, tableSettingColumn) &&
73
+ <ColumnComponent
74
+ className={classNames(this.columnClassNamesForColumn(column))}
75
+ data-identifier={columnIdentifier(column)}
76
+ key={columnIdentifier(column)}
77
+ {...this.props.liveTable.columnProps(column)}
78
+ >
79
+ <div className="live-table-column-label">
80
+ {this.props.liveTable.headerLabelForColumn(column)}
81
+ </div>
82
+ <div className="live-table-column-value">
83
+ {column.content && this.columnContentFromContentArg(column, model)}
84
+ {!column.content && column.attribute && this.columnsContentFromAttributeAndPath(column, model)}
85
+ </div>
86
+ </ColumnComponent>
87
+ )
88
+ }
89
+
90
+ columnContentFromContentArg (column, model) {
91
+ const value = column.content(this.modelCallbackArgs)
92
+
93
+ return this.presentColumnValue(value)
94
+ }
95
+
96
+ columnsContentFromAttributeAndPath (column, model) {
97
+ const {attribute} = digs(column, "attribute")
98
+ const currentModelClass = this.props.modelClass
99
+ const path = column.path || []
100
+
101
+ let currentModel = model
102
+
103
+ if (path.length > 0) {
104
+ for (const pathPart of path) {
105
+ currentModel = currentModel[pathPart]()
106
+ if (!currentModel) return
107
+ }
108
+ }
109
+
110
+ if (!(attribute in currentModel)) throw new Error(`${currentModelClass.modelName().name} doesn't respond to ${attribute}`)
111
+
112
+ const value = currentModel[attribute]()
113
+
114
+ return this.presentColumnValue(value)
115
+ }
116
+
117
+ _modelCallbackArgs () {
118
+ const {model} = digs(this.props, "model")
119
+ const modelArgName = inflection.camelize(this.props.liveTable.props.modelClass.modelClassData().name, true)
120
+ const modelCallbackArgs = {}
121
+
122
+ modelCallbackArgs[modelArgName] = model
123
+
124
+ return modelCallbackArgs
125
+ }
126
+
127
+ onDestroyClicked = async (e) => {
128
+ e.preventDefault()
129
+
130
+ const {destroyMessage} = digg(this, "props", "liveTable", "props")
131
+ const {model} = digs(this.props, "model")
132
+
133
+ if (!confirm(I18n.t("js.shared.are_you_sure"))) {
134
+ return
135
+ }
136
+
137
+ try {
138
+ await model.destroy()
139
+
140
+ if (destroyMessage) {
141
+ FlashMessage.success(destroyMessage)
142
+ }
143
+ } catch (error) {
144
+ FlashMessage.errorResponse(error)
145
+ }
146
+ }
147
+
148
+ presentColumnValue (value) {
149
+ if (value instanceof Date) {
150
+ return this.presentDateTime(value)
151
+ } else if (MoneyFormatter.isMoney(value)) {
152
+ return MoneyFormatter.format(value)
153
+ } else if (typeof value == "boolean") {
154
+ if (value) return I18n.t("js.shared.yes")
155
+
156
+ return I18n.t("js.shared.no")
157
+ } else if (Array.isArray(value)) {
158
+ return value
159
+ .map((valuePart) => this.presentColumnValue(valuePart))
160
+ .filter((valuePart) => Boolean(valuePart))
161
+ .join(", ")
162
+ }
163
+
164
+ return value
165
+ }
166
+
167
+ presentDateTime(value) {
168
+ const apiMakerType = value.apiMakerType || "time"
169
+
170
+ if (apiMakerType == "time") {
171
+ const dateTimeFormatName = this.props.liveTable.props.defaultDateTimeFormatName || "time.formats.default"
172
+
173
+ return I18n.l(dateTimeFormatName, value)
174
+ } else if (apiMakerType == "date") {
175
+ const dateFormatName = this.props.liveTable.props.defaultDateTimeFormatName || "date.formats.default"
176
+
177
+ return I18n.l(dateFormatName, value)
178
+ } else {
179
+ throw new Error(`Unhandled type: ${apiMakerType}`)
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,48 @@
1
+ import {digg, digs} from "diggerize"
2
+ import inflection from "inflection"
3
+ import modelClassRequire from "../model-class-require.mjs"
4
+
5
+ class SelectCalculator {
6
+ constructor({table}) {
7
+ this.table = table
8
+ }
9
+
10
+ selects() {
11
+ const {modelClass} = digs(this.table.props, "modelClass")
12
+ const select = this.table.props.select || {}
13
+ const {preparedColumns} = digs(this.table.shape, "preparedColumns")
14
+
15
+ for (const preparedColumn of preparedColumns) {
16
+ const {column} = digs(preparedColumn, "column")
17
+
18
+ if (!column?.attribute) continue // 'column' might not exist if has been removed in code but still saved in DB
19
+
20
+ const {attribute} = digs(column, "attribute")
21
+ const {path} = column
22
+
23
+ let currentModelClass = modelClass
24
+
25
+ if (path) {
26
+ for (const pathPart of path) {
27
+ const relationships = digg(currentModelClass.modelClassData(), "relationships")
28
+ const relationship = relationships.find((relationshipInArray) => relationshipInArray.name == inflection.underscore(pathPart))
29
+
30
+ if (!relationship) throw new Error(`No such relationship: ${currentModelClass.modelClassData().name}#${pathPart}`)
31
+
32
+ currentModelClass = modelClassRequire(digg(relationship, "className"))
33
+ }
34
+ }
35
+
36
+ const currentModelClassName = digg(currentModelClass.modelClassData(), "name")
37
+
38
+ if (!(currentModelClassName in select)) select[currentModelClassName] = []
39
+ if (!select[currentModelClassName].includes(attribute)) select[currentModelClassName].push(attribute)
40
+ }
41
+
42
+ return select
43
+ }
44
+ }
45
+
46
+ export default function selectCalculator(...props) {
47
+ return new SelectCalculator(...props).selects()
48
+ }
@@ -0,0 +1,72 @@
1
+ @import "./variables";
2
+
3
+ .component-api-maker-live-table {
4
+ .live-table-header {
5
+ text-align: left;
6
+ }
7
+
8
+ @media (max-width: $sm-to) {
9
+ .live-table-column {
10
+ display: flex;
11
+ justify-content: space-between;
12
+
13
+ .live-table-column-value {
14
+ text-align: right;
15
+ }
16
+ }
17
+
18
+ .live-table-header-row {
19
+ margin-bottom: 15px;
20
+ }
21
+
22
+ .live-table-row {
23
+ + .live-table-row {
24
+ margin-top: 15px;
25
+ }
26
+ }
27
+ }
28
+
29
+ @media (min-width: $md-from) {
30
+ .actions-column {
31
+ text-align: right;
32
+ }
33
+
34
+ .live-table-header:not(:first-child),
35
+ .live-table-column:not(:first-child) {
36
+ padding-left: 10px;
37
+ }
38
+
39
+ .live-table-header:not(:last-child),
40
+ .live-table-column:not(:last-child) {
41
+ padding-right: 10px;
42
+ }
43
+
44
+ .live-table-column {
45
+ .live-table-column-label {
46
+ display: none;
47
+ }
48
+
49
+ &[data-text-align="center"] {
50
+ .live-table-column-value {
51
+ text-align: center;
52
+ }
53
+ }
54
+
55
+ &[data-text-align="right"] {
56
+ .live-table-column-value {
57
+ text-align: right;
58
+ }
59
+ }
60
+ }
61
+
62
+ .live-table-header {
63
+ &[data-text-align="center"] {
64
+ text-align: center;
65
+ }
66
+
67
+ &[data-text-align="right"] {
68
+ text-align: right;
69
+ }
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,175 @@
1
+ import columnIdentifier from "./column-identifier.mjs"
2
+ import columnVisible from "./column-visible.mjs"
3
+ import {digg} from "diggerize"
4
+ import inflection from "inflection"
5
+ import {serialize as objectToFormData} from "object-to-formdata"
6
+ import {TableSetting} from "../models.mjs.erb"
7
+
8
+ export default class ApiMakerTableSettings {
9
+ constructor({table}) {
10
+ this.table = table
11
+ }
12
+
13
+ columns = () => digg(this, "table", "columnsAsArray")()
14
+ currentUser = () => digg(this, "table", "props", "currentUser")
15
+ identifier = () => digg(this, "table", "shape", "identifier")
16
+
17
+ preparedColumns = (tableSetting) => {
18
+ const columns = this.table.columnsAsArray()
19
+ const ordered = this.orderedTableSettingColumns(tableSetting)
20
+ const result = {
21
+ columns: [],
22
+ preload: []
23
+ }
24
+
25
+ if (!ordered) return
26
+
27
+ for (const tableSettingColumn of ordered) {
28
+ const column = columns.find((column) => columnIdentifier(column) == tableSettingColumn.identifier())
29
+
30
+ result.columns.push({column, tableSettingColumn})
31
+
32
+ // Add needed preloads if column is visible
33
+ if (columnVisible(column, tableSettingColumn)) {
34
+ if (column.path) {
35
+ const preload = column.path.map((pathPart) => inflection.underscore(pathPart)).join(".")
36
+
37
+ if (!result.preload.includes(preload)) result.preload.push(preload)
38
+ }
39
+ }
40
+ }
41
+
42
+ return result
43
+ }
44
+
45
+ orderedTableSettingColumns = (tableSetting) => {
46
+ return tableSetting
47
+ .columns()
48
+ .loaded()
49
+ .sort((tableSettingColumn1, tableSettingColumn2) => tableSettingColumn1.position() - tableSettingColumn2.position())
50
+ }
51
+
52
+ loadExistingOrCreateTableSettings = async () => {
53
+ let tableSetting = await this.loadTableSetting()
54
+
55
+ if (tableSetting) {
56
+ tableSetting = await this.updateTableSetting(tableSetting)
57
+ } else {
58
+ tableSetting = await this.createInitialTableSetting()
59
+ }
60
+
61
+ return tableSetting
62
+ }
63
+
64
+ loadTableSetting = async () => {
65
+ if (!TableSetting) throw new Error("TableSetting model couldn't be imported")
66
+
67
+ const tableSetting = await TableSetting
68
+ .ransack({
69
+ identifier_eq: this.identifier(),
70
+ user_id_eq: this.currentUser().id(),
71
+ user_type_eq: digg(this.currentUser().modelClassData(), "name")
72
+ })
73
+ .preload("columns")
74
+ .first()
75
+
76
+ return tableSetting
77
+ }
78
+
79
+ createInitialTableSetting = async () => {
80
+ const tableSettingData = {
81
+ identifier: this.identifier(),
82
+ user_id: this.currentUser().id(),
83
+ user_type: digg(this.currentUser().modelClassData(), "name"),
84
+ columns_attributes: {}
85
+ }
86
+
87
+ const columns = this.columns()
88
+
89
+ for (const columnKey in columns) {
90
+ const column = digg(columns, columnKey)
91
+ const identifier = columnIdentifier(column)
92
+ const columnData = this.columnSaveData(column, {identifier, position: columnKey})
93
+
94
+ tableSettingData.columns_attributes[columnKey] = columnData
95
+ }
96
+
97
+ const tableSetting = new TableSetting()
98
+ const tableSettingFormData = objectToFormData({table_setting: tableSettingData})
99
+
100
+ await tableSetting.saveRaw(tableSettingFormData)
101
+
102
+ const reloadedTableSetting = await this.loadTableSetting()
103
+
104
+ return reloadedTableSetting
105
+ }
106
+
107
+ columnSaveData(column, {identifier, position}) {
108
+ return {
109
+ attribute_name: column.attribute,
110
+ identifier,
111
+ path: column.path,
112
+ position,
113
+ sort_key: column.sortKey,
114
+ visible: null
115
+ }
116
+ }
117
+
118
+ updateTableSetting = async (tableSetting) => {
119
+ // This should remove columns no longer found
120
+ // This should update columns that have changed
121
+
122
+ const columns = this.columns()
123
+ const columnsData = {}
124
+ const tableSettingData = {columns_attributes: columnsData}
125
+ let columnsKeyCount = 0
126
+ let changed = false
127
+
128
+ // Add missing columns
129
+ for (const column of columns) {
130
+ const identifier = columnIdentifier(column)
131
+ const tableSettingColumn = tableSetting.columns().loaded().find((tableSettingColumn) => tableSettingColumn.identifier() == identifier)
132
+
133
+ if (!tableSettingColumn) {
134
+ const columnKey = ++columnsKeyCount
135
+
136
+ columnsData[columnKey] = this.columnSaveData(
137
+ column,
138
+ {
139
+ identifier,
140
+ position: tableSetting.columns().loaded().length + columnKey
141
+ }
142
+ )
143
+
144
+ changed = true
145
+ }
146
+ }
147
+
148
+ for (const tableSettingColumn of tableSetting.columns().loaded()) {
149
+ const column = columns.find((column) => columnIdentifier(column) == tableSettingColumn.identifier())
150
+
151
+ if (column) {
152
+ // Update column if changed
153
+ } else {
154
+ // Removed saved columns no longer found
155
+ const columnKey = ++columnsKeyCount
156
+
157
+ columnsData[columnKey] = {
158
+ id: tableSettingColumn.id(),
159
+ _destroy: true
160
+ }
161
+ }
162
+ }
163
+
164
+ if (changed) {
165
+ const tableSettingFormData = objectToFormData({table_setting: tableSettingData})
166
+
167
+ await tableSetting.saveRaw(tableSettingFormData)
168
+
169
+ // Maybe not necessary?
170
+ // tableSetting = this.loadTableSetting()
171
+ }
172
+
173
+ return tableSetting
174
+ }
175
+ }