@things-factory/meta-ui 5.0.0-zeta.1

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 (45) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE.md +21 -0
  3. package/client/actions/main.js +1 -0
  4. package/client/bootstrap.js +8 -0
  5. package/client/index.js +23 -0
  6. package/client/mixin/handler-basic-button-mixin.js +202 -0
  7. package/client/mixin/handler-common-button-mixin.js +90 -0
  8. package/client/mixin/handler-custom-button-mixin.js +127 -0
  9. package/client/mixin/handler-graphql-mixin.js +297 -0
  10. package/client/mixin/handler-grist-button-mixin.js +119 -0
  11. package/client/mixin/meta-set-mixin.js +69 -0
  12. package/client/mixin/meta-util-mixin.js +577 -0
  13. package/client/mixin/render-basic-form-mixin.js +264 -0
  14. package/client/mixin/render-basic-grist-mixin.js +85 -0
  15. package/client/mixin/render-button-mixin.js +163 -0
  16. package/client/mixin/render-grist-mixin.js +478 -0
  17. package/client/mixin/render-search-mixin.js +37 -0
  18. package/client/pages/basic-form-element.js +9 -0
  19. package/client/pages/basic-grist-element.js +11 -0
  20. package/client/pages/basic-grist-page.js +12 -0
  21. package/client/pages/main.js +27 -0
  22. package/client/reducers/main.js +17 -0
  23. package/client/route.js +7 -0
  24. package/dist-server/controllers/index.js +1 -0
  25. package/dist-server/controllers/index.js.map +1 -0
  26. package/dist-server/index.js +20 -0
  27. package/dist-server/index.js.map +1 -0
  28. package/dist-server/middlewares/index.js +8 -0
  29. package/dist-server/middlewares/index.js.map +1 -0
  30. package/dist-server/migrations/index.js +12 -0
  31. package/dist-server/migrations/index.js.map +1 -0
  32. package/dist-server/routes.js +25 -0
  33. package/dist-server/routes.js.map +1 -0
  34. package/package.json +30 -0
  35. package/server/controllers/index.ts +0 -0
  36. package/server/index.ts +4 -0
  37. package/server/middlewares/index.ts +3 -0
  38. package/server/migrations/index.ts +9 -0
  39. package/server/routes.ts +28 -0
  40. package/things-factory.config.js +13 -0
  41. package/translations/en.json +1 -0
  42. package/translations/ko.json +1 -0
  43. package/translations/ms.json +1 -0
  44. package/translations/zh.json +1 -0
  45. package/tsconfig.json +9 -0
@@ -0,0 +1,264 @@
1
+ import { css, html } from 'lit'
2
+ import { RenderButtonMixin } from "./render-button-mixin";
3
+ import { ScrollbarStyles } from '@operato/styles'
4
+ import { getEditor, getRenderer } from '@operato/data-grist'
5
+
6
+ /**
7
+ * 메타 서비스를 이용하는 페이지
8
+ ******************************************************
9
+ * @param {Object} baseElement
10
+ * @returns
11
+ */
12
+ export const RenderBasicFormMixin = (baseElement) => class extends RenderButtonMixin(baseElement) {
13
+ static get styles() {
14
+ let styles = [];
15
+
16
+ let topStyles = [
17
+ ScrollbarStyles,
18
+ css`
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+ overflow-x: overlay;
23
+ background-color: var(--main-section-background-color);
24
+ }
25
+
26
+ ox-record-view-body {
27
+ flex: 1;
28
+ overflow-y: auto;
29
+ }
30
+ `
31
+ ];
32
+
33
+ styles.push(...topStyles);
34
+ styles.push(...this.getButtonContainerStyle());
35
+
36
+ return styles;
37
+ }
38
+
39
+
40
+ /**
41
+ * 데이터 조회
42
+ */
43
+ async fetchHandler() {
44
+ let data = await this.gqlFindOne(this.parent_id);
45
+ data['__seq__'] = 1;
46
+ let orgData = {};
47
+
48
+ Object.assign(orgData, data);
49
+ data['__origin__'] = orgData;
50
+ this.viewBody.record = { ...data };
51
+ }
52
+
53
+ /**
54
+ * 메뉴 메타 정보를 기반으로 form 설정 셋을 생성 한다.
55
+ ************************************************
56
+ * @returns {Array} Form 설정 셋
57
+ */
58
+ async getFormConfigSet() {
59
+ // 폼에 사용할 수 있는 설정 정보가 없으면
60
+ if (this.isEmpty(this.formColumnConfig) && this.isEmpty(this.gridColumnConfig)) {
61
+ return [];
62
+ }
63
+
64
+ // 메타에 폼 컬럼 정보가 없으면
65
+ if (this.isEmpty(this.formColumnConfig)) {
66
+ // 그리드 설정을 사용한다.
67
+ this.formColumnConfig = this.gridColumnConfig;
68
+ }
69
+
70
+ let columns = [];
71
+ for (let idx = 0; idx < this.formColumnConfig.length; idx++) {
72
+ var {
73
+ type = "string",
74
+ name = undefined,
75
+ header = undefined,
76
+ hidden = false,
77
+ editable = true,
78
+ mandatory = false,
79
+ align = "left",
80
+ select_opt = undefined,
81
+ object_opt = undefined
82
+ } = this.formColumnConfig[idx];
83
+
84
+ // 컬럼 config 으로 변경 및 문자열 처리
85
+ let column = {
86
+ type: type,
87
+ name: name,
88
+ header_txt: this.isEmpty(header) ? this.convertMsgCode(`field.${name}`) : this.convertMsgCode(header),
89
+ header: {
90
+ renderer: function (column) {
91
+ return column.header_txt;
92
+ }
93
+ },
94
+ hidden: hidden,
95
+ record: {
96
+ editable: editable,
97
+ mandatory: mandatory,
98
+ align: align,
99
+ classifier: function () { },
100
+ renderer: getRenderer(type)
101
+ }
102
+ };
103
+
104
+ if (editable) {
105
+ column.record.editor = getEditor(type);
106
+ }
107
+
108
+ // select-option
109
+ if (type === 'select' && this.isNotEmpty(select_opt)) {
110
+ if (Array.isArray(select_opt)) {
111
+ column.record.options = select_opt;
112
+ } else {
113
+ if (select_opt.type === 'code') { // 공통 코드
114
+ column.record.options = await this.getCommonCodesByName(select_opt.name);
115
+ } else if (select_opt.type === 'scenario') { // 시나리오
116
+ column.record.options = await this.getCodeByScenario(select_opt.name, select_opt.args)
117
+ }
118
+ }
119
+ }
120
+
121
+ // object-option
122
+ if (type === 'object' && this.isNotEmpty(object_opt)) {
123
+ column.record.options = object_opt;
124
+ }
125
+
126
+ columns.push(column);
127
+ }
128
+ return columns;
129
+ }
130
+
131
+
132
+ /**
133
+ * 폼뷰 저장 버튼 override
134
+ */
135
+ async save() {
136
+ let recordData = this.viewBody.record;
137
+
138
+ // 변경 여부 메시지 처리
139
+ if (this.isEmpty(recordData.__dirty__)) {
140
+ this.showCustomAlert('title.info', 'text.NOTHING_CHANGED');
141
+ return;
142
+ }
143
+
144
+ let data = recordData.__dirtyfields__;
145
+ let dirtyData = {};
146
+
147
+ if (this.isNotEmpty(recordData.id)) {
148
+ dirtyData.id = recordData.id;
149
+ }
150
+
151
+ Object.keys(data).forEach(key => {
152
+ dirtyData[key] = data[key].after;
153
+ })
154
+
155
+ let patches = [{ ...dirtyData, cuFlag: recordData.__dirty__ }]
156
+ await this.requestCUMultiple(patches);
157
+ return true;
158
+ }
159
+
160
+ /**********************************
161
+ * LifeCycle
162
+ ***********************************/
163
+
164
+ async connectedCallback() {
165
+ this.getMetaInfo('master', 'operation-master');
166
+ // 버튼 설정에서 아래 항목 제외
167
+ this.buttonConfig = this.buttonConfig.filter(x => x.name != 'export' && x.name != 'import' && x.name != 'add' && x.name != 'delete');
168
+
169
+ if (this.isElement) {
170
+ // form 구성 설정 셋
171
+ this.formConfigSet = await this.getFormConfigSet();
172
+ }
173
+
174
+ // 폼뷰의 인풋에서 발생되는 변경 이벤트 처리
175
+ this.addEventListener('field-change', e => {
176
+ var { after, before, column, record, row } = e.detail;
177
+
178
+ /* compare changes */
179
+ if (this.isEquals(after, before)) {
180
+ return
181
+ }
182
+
183
+ var validation = column.validation
184
+ if (validation && typeof validation == 'function') {
185
+ if (!validation.call(this, after, before, record, column)) {
186
+ return
187
+ }
188
+ }
189
+
190
+ let colName = column.name;
191
+
192
+ // 변경 값 셋팅
193
+ record[colName] = after;
194
+
195
+ // 변경 필드 정보
196
+ record.__dirtyfields__ = record.__dirtyfields__ || {};
197
+ record.__dirtyfields__[colName] = {
198
+ before: record.__origin__[colName],
199
+ after: record[colName]
200
+ }
201
+
202
+ // 같은 값으로 변경 되었다면
203
+ if (this.isEquals(record.__dirtyfields__[colName].before, record.__dirtyfields__[colName].after)) {
204
+ delete record.__dirtyfields__[colName];
205
+ }
206
+
207
+ if (this.isNotEmpty(record.__dirtyfields__)) {
208
+ // flag
209
+ record.__dirty__ = 'M';
210
+ } else {
211
+ // flag
212
+ record.__dirty__ = '';
213
+ }
214
+
215
+ this.viewBody.record = { ...record };
216
+ })
217
+
218
+ if (super.connectedCallback) {
219
+ await super.connectedCallback();
220
+ }
221
+ }
222
+
223
+ /**
224
+ * 화면이 element 인 경우
225
+ */
226
+ async firstUpdated() {
227
+ if (super.firstUpdated) {
228
+ await super.firstUpdated();
229
+ }
230
+
231
+ await this.fetch();
232
+ }
233
+
234
+ /**
235
+ * 화면이 페이지인 경우
236
+ */
237
+ async pageInitialized() {
238
+ if (this.isPage) {
239
+ // form 구성 설정 셋
240
+ this.formConfigSet = await this.getFormConfigSet();
241
+ }
242
+
243
+ if (super.pageInitialized) {
244
+ await super.pageInitialized();
245
+ }
246
+ }
247
+
248
+ get viewBody() {
249
+ return this.renderRoot.querySelector('ox-record-view-body');
250
+ }
251
+
252
+ render() {
253
+ return html`
254
+ <ox-record-view-body .columns=${this.formConfigSet} .record=${{}} .rowIndex=${1}>
255
+ </ox-record-view-body>
256
+
257
+ ${this.getButtonContainer()}
258
+ `;
259
+ }
260
+
261
+ get context() {
262
+ return this.getContextObject();
263
+ }
264
+ }
@@ -0,0 +1,85 @@
1
+ import { RenderGristMixin } from "./render-grist-mixin";
2
+
3
+ import { css, html } from 'lit'
4
+ import { i18next } from "@operato/i18n";
5
+
6
+ /**
7
+ * 메타 서비스를 이용하는 페이지
8
+ ******************************************************
9
+ * @param {Object} baseElement
10
+ * @returns
11
+ */
12
+ export const RenderBasicGristMixin = (baseElement) => class extends RenderGristMixin(baseElement) {
13
+ static get styles() {
14
+ let styles = [];
15
+
16
+
17
+ let topStyles = [
18
+ css`
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+ overflow-x: overlay;
23
+ background-color: var(--main-section-background-color);
24
+ }
25
+ `
26
+ ];
27
+
28
+ styles.push(...topStyles);
29
+ styles.push(...this.gristStyles())
30
+ styles.push(...this.getButtonContainerStyle());
31
+
32
+ return styles;
33
+ }
34
+
35
+ /**********************************
36
+ * LifeCycle
37
+ ***********************************/
38
+
39
+ async connectedCallback() {
40
+ this.getMetaInfo('master', 'operation-master');
41
+
42
+ if (this.isElement) {
43
+ // 그리스트 구성 설정 셋
44
+ this.gristConfigSet = await this.getGristConfigSet();
45
+ }
46
+
47
+ if (super.connectedCallback) {
48
+ await super.connectedCallback();
49
+ }
50
+ }
51
+
52
+ /**
53
+ * 화면이 element 인 경우
54
+ */
55
+ async firstUpdated() {
56
+ if (super.firstUpdated) {
57
+ await super.firstUpdated();
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 화면이 페이지인 경우
63
+ */
64
+ async pageInitialized() {
65
+ if (this.isPage) {
66
+ // 그리스트 구성 설정 셋
67
+ this.gristConfigSet = await this.getGristConfigSet();
68
+ }
69
+
70
+ if (super.pageInitialized) {
71
+ await super.pageInitialized();
72
+ }
73
+ }
74
+
75
+ render() {
76
+ return html`
77
+ ${this.getGristHtml()}
78
+ ${this.getButtonContainer()}
79
+ `;
80
+ }
81
+
82
+ get context() {
83
+ return this.getContextObject();
84
+ }
85
+ }
@@ -0,0 +1,163 @@
1
+ import { CommonButtonStyles } from '@operato/styles'
2
+ import { HandlerGristButtonMixin } from "./handler-grist-button-mixin";
3
+ import { css, html } from 'lit'
4
+
5
+ /**
6
+ * 메타 서비스를 이용해 버튼 렌더링
7
+ ******************************************************
8
+ * @param {Object} baseElement
9
+ * @returns
10
+ */
11
+ export const RenderButtonMixin = (baseElement) => class extends HandlerGristButtonMixin(baseElement) {
12
+
13
+ /**
14
+ * 버튼 컨테이너 스타일
15
+ */
16
+ static getButtonContainerStyle() {
17
+ return [css`
18
+ .button-container {
19
+ padding: var(--button-container-padding);
20
+ margin: var(--button-container-margin);
21
+ text-align: var(--button-container-align);
22
+ background-color: var(--button-container-background);
23
+ height: var(--button-container-height);
24
+ text-align: right;
25
+ padding-right: 12px;
26
+ }
27
+ `]
28
+ }
29
+
30
+ /**
31
+ * 메뉴 컨텍스트에서 사용할 버튼 정보 추출
32
+ * @returns {Array}
33
+ */
34
+ getContextButtons() {
35
+ var { use_import = false, use_export = false, use_add = false } = {};
36
+
37
+ let retObj = [];
38
+
39
+ this.buttonConfig.forEach(x => {
40
+ if (x.name == 'export') use_export = true;
41
+ else if (x.name == 'import') use_import = true;
42
+ else if (x.name == 'add') use_add = true;
43
+ else {
44
+ let btn = this.getContextButtonSet(x);
45
+ if (this.isNotEmpty(btn)) {
46
+ retObj.push(btn);
47
+ }
48
+ }
49
+ });
50
+
51
+ this.use_button_export = use_export;
52
+ this.use_button_import = use_import;
53
+ this.use_button_add = use_add;
54
+
55
+ return retObj;
56
+ }
57
+
58
+ /**
59
+ * 버튼 설정에서 페이지 context 에서 리턴될 버튼 오브젝트 생성
60
+ * @param {Object} btnObj
61
+ * @returns
62
+ */
63
+ getContextButtonSet(btnObj) {
64
+ var {
65
+ name = undefined,
66
+ label = undefined,
67
+ style = undefined,
68
+ type = 'basic',
69
+ action = undefined,
70
+ logic = undefined
71
+ } = btnObj;
72
+
73
+ if (this.isEmpty(name)) {
74
+ return undefined;
75
+ }
76
+
77
+ if (this.isEmpty(label)) label = `button.${name}`;
78
+ if (this.isEmpty(style)) style = name;
79
+ if (this.isEmpty(action)) action = name;
80
+
81
+ return {
82
+ title: this.convertMsgCode(label),
83
+ action: this.getButtonActionHandler(type, action, logic),
84
+ ...(this.isNotEmpty(style) ? CommonButtonStyles[style] : [])
85
+ }
86
+ }
87
+
88
+
89
+ /**
90
+ * element 로 로드시 버튼을 포함한 container div 를 리턴한다.
91
+ * @returns {HTML}
92
+ */
93
+ getButtonContainer() {
94
+ if (this.isPage) return html``;
95
+
96
+ return html`<div id="button-container" class="button-container">${this.getContainerButtons()}</div>`;
97
+ }
98
+
99
+ /**
100
+ * element 로 로드시 버튼 element 를 container 에 직접 그려야함.
101
+ */
102
+ getContainerButtons() {
103
+ let retObj = [];
104
+
105
+ this.buttonConfig.forEach(x => {
106
+ var {
107
+ name = undefined,
108
+ label = undefined,
109
+ style = undefined,
110
+ type = 'basic',
111
+ action = undefined,
112
+ logic = undefined
113
+ } = x;
114
+
115
+ if (this.isEmpty(label)) label = `button.${name}`;
116
+ if (this.isEmpty(style)) style = name;
117
+ if (this.isEmpty(action)) action = name;
118
+
119
+ let btn = this.createButtonElement(this.convertMsgCode(label));
120
+ btn.onclick = this.getButtonActionHandler(type, action, logic);
121
+ retObj.push(btn);
122
+ });
123
+
124
+ return retObj;
125
+ }
126
+
127
+ /**
128
+ * 버튼 엘리먼트를 생성해 리턴한다.
129
+ * **************************************
130
+ * @param {String} buttonName
131
+ * @returns
132
+ */
133
+ createButtonElement(buttonName) {
134
+ let btnHtml = `<mwc-button raised label="${buttonName}" style="margin-left:7px;margin-top:7px;"></mwc-button>`
135
+ let btnEle = this.htmlToElement(btnHtml);
136
+
137
+ return btnEle;
138
+ }
139
+
140
+
141
+ /**********************************
142
+ * LifeCycle
143
+ ***********************************/
144
+
145
+ async connectedCallback() {
146
+ if (super.connectedCallback) {
147
+ await super.connectedCallback();
148
+ }
149
+ }
150
+
151
+ async firstUpdated() {
152
+ if (super.firstUpdated) {
153
+ await super.firstUpdated();
154
+ }
155
+ }
156
+
157
+ async pageInitialized() {
158
+ if (super.pageInitialized) {
159
+ await super.pageInitialized();
160
+ }
161
+ }
162
+
163
+ }