@operato/data-grist 2.0.0-beta.3 → 2.0.0-beta.30

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 (60) hide show
  1. package/.storybook/preview.js +29 -0
  2. package/CHANGELOG.md +192 -0
  3. package/dist/src/data-card/data-card-gutter-menu.js +2 -0
  4. package/dist/src/data-card/data-card-gutter-menu.js.map +1 -1
  5. package/dist/src/data-card/record-card.js +4 -4
  6. package/dist/src/data-card/record-card.js.map +1 -1
  7. package/dist/src/data-grid/data-grid-body.js +3 -0
  8. package/dist/src/data-grid/data-grid-body.js.map +1 -1
  9. package/dist/src/data-grid/data-grid-field.d.ts +1 -0
  10. package/dist/src/data-grid/data-grid-field.js +2 -0
  11. package/dist/src/data-grid/data-grid-field.js.map +1 -1
  12. package/dist/src/data-grid/data-grid-header.d.ts +1 -0
  13. package/dist/src/data-grid/data-grid-header.js +3 -11
  14. package/dist/src/data-grid/data-grid-header.js.map +1 -1
  15. package/dist/src/data-grist.js +1 -1
  16. package/dist/src/data-grist.js.map +1 -1
  17. package/dist/src/data-list/record-partial.js +4 -4
  18. package/dist/src/data-list/record-partial.js.map +1 -1
  19. package/dist/src/editors/ox-grist-editor-date.js +1 -1
  20. package/dist/src/editors/ox-grist-editor-date.js.map +1 -1
  21. package/dist/src/editors/ox-grist-editor-datetime.js +1 -1
  22. package/dist/src/editors/ox-grist-editor-datetime.js.map +1 -1
  23. package/dist/src/filters/filter-checkbox.js +1 -1
  24. package/dist/src/filters/filter-checkbox.js.map +1 -1
  25. package/dist/src/personalizer/ox-grist-filter-personalizer.js +1 -1
  26. package/dist/src/personalizer/ox-grist-filter-personalizer.js.map +1 -1
  27. package/dist/src/personalizer/ox-grist-personalizer.js +42 -11
  28. package/dist/src/personalizer/ox-grist-personalizer.js.map +1 -1
  29. package/dist/src/renderers/ox-grist-renderer-json5.js +3 -0
  30. package/dist/src/renderers/ox-grist-renderer-json5.js.map +1 -1
  31. package/dist/src/renderers/ox-grist-renderer-text.js +2 -12
  32. package/dist/src/renderers/ox-grist-renderer-text.js.map +1 -1
  33. package/dist/stories/dynamic-editable.stories.js +36 -0
  34. package/dist/stories/dynamic-editable.stories.js.map +1 -1
  35. package/dist/stories/grid-setting.stories.js +6 -1
  36. package/dist/stories/grid-setting.stories.js.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +13 -11
  39. package/src/data-card/data-card-gutter-menu.ts +2 -0
  40. package/src/data-card/record-card.ts +4 -4
  41. package/src/data-grid/data-grid-body.ts +4 -0
  42. package/src/data-grid/data-grid-field.ts +3 -1
  43. package/src/data-grid/data-grid-header.ts +4 -12
  44. package/src/data-grist.ts +1 -1
  45. package/src/data-list/record-partial.ts +4 -4
  46. package/src/editors/ox-grist-editor-date.ts +1 -1
  47. package/src/editors/ox-grist-editor-datetime.ts +1 -1
  48. package/src/filters/filter-checkbox.ts +1 -1
  49. package/src/personalizer/ox-grist-filter-personalizer.ts +1 -1
  50. package/src/personalizer/ox-grist-personalizer.ts +42 -11
  51. package/src/renderers/ox-grist-renderer-json5.ts +4 -0
  52. package/src/renderers/ox-grist-renderer-text.ts +2 -14
  53. package/stories/dynamic-editable.stories.ts +36 -0
  54. package/stories/grid-setting.stories.ts +6 -1
  55. package/themes/grist-theme.css +4 -0
  56. package/translations/en.json +3 -0
  57. package/translations/ja.json +2 -0
  58. package/translations/ko.json +2 -0
  59. package/translations/ms.json +2 -0
  60. package/translations/zh.json +2 -0
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@operato/data-grist",
3
- "version": "2.0.0-beta.3",
3
+ "version": "2.0.0-beta.30",
4
4
  "description": "User interface for grid (desktop) and list (mobile)",
5
5
  "author": "heartyoh",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
8
8
  "license": "MIT",
9
+ "operato": true,
9
10
  "publishConfig": {
10
11
  "access": "public",
11
12
  "@operato:registry": "https://registry.npmjs.org"
@@ -17,6 +18,7 @@
17
18
  },
18
19
  "exports": {
19
20
  ".": "./dist/src/index.js",
21
+ "./package.json": "./package.json",
20
22
  "./ox-grist.js": "./dist/src/data-grist.js",
21
23
  "./ox-report.js": "./dist/src/data-report.js",
22
24
  "./ox-filters-form.js": "./dist/src/filters/filters-form.js",
@@ -59,16 +61,16 @@
59
61
  "storybook:build": "tsc && build-storybook"
60
62
  },
61
63
  "dependencies": {
62
- "@material/web": "^1.4.0",
63
- "@operato/headroom": "^2.0.0-beta.0",
64
- "@operato/input": "^2.0.0-beta.3",
65
- "@operato/p13n": "^2.0.0-beta.3",
66
- "@operato/popup": "^2.0.0-beta.0",
67
- "@operato/pull-to-refresh": "^2.0.0-beta.0",
68
- "@operato/styles": "^2.0.0-beta.0",
64
+ "@material/web": "^1.5.0",
65
+ "@operato/headroom": "^2.0.0-beta.23",
66
+ "@operato/input": "^2.0.0-beta.30",
67
+ "@operato/p13n": "^2.0.0-beta.30",
68
+ "@operato/popup": "^2.0.0-beta.30",
69
+ "@operato/pull-to-refresh": "^2.0.0-beta.23",
70
+ "@operato/styles": "^2.0.0-beta.23",
69
71
  "@operato/time-calculator": "^2.0.0-beta.0",
70
- "@operato/utils": "^2.0.0-beta.0",
71
- "i18next": "^21.5.4",
72
+ "@operato/utils": "^2.0.0-beta.23",
73
+ "i18next": "^21.6.3",
72
74
  "json5": "^2.2.0",
73
75
  "lit": "^3.1.2",
74
76
  "lodash-es": "^4.17.21"
@@ -106,5 +108,5 @@
106
108
  "prettier --write"
107
109
  ]
108
110
  },
109
- "gitHead": "f784d266745412a10887dd5163da8c8d7a4fe423"
111
+ "gitHead": "b45f30f4c78bc715f5c5581cf97505b3b6372964"
110
112
  }
@@ -26,6 +26,8 @@ class DataCardGutterMenu extends LitElement {
26
26
  border-radius: var(--data-card-item-btn-border-radius);
27
27
  padding: var(--data-card-item-btn-padding);
28
28
  font-size: var(--md-sys-typescale-label-large-size, 0.875rem);
29
+ background-color: transparent;
30
+ color: var(--grid-record-color);
29
31
  }
30
32
 
31
33
  md-icon:hover {
@@ -59,7 +59,7 @@ export class RecordCard extends LitElement {
59
59
  text-indent: 1px;
60
60
  left: var(--spacing-none);
61
61
  top: var(--spacing-none);
62
- color: var(--grid-record-dirty-color);
62
+ color: var(--grid-record-dirty-color, var(--md-sys-color-error));
63
63
  }
64
64
 
65
65
  [thumbnail] {
@@ -102,17 +102,17 @@ export class RecordCard extends LitElement {
102
102
 
103
103
  ox-card-field {
104
104
  font: var(--data-card-item-etc-font);
105
- color: var(--data-card-item-etc-color);
105
+ color: var(--data-card-item-etc-color, var(--md-sys-color-on-surface));
106
106
  }
107
107
 
108
108
  ox-card-field[name] {
109
109
  font: var(--data-card-item-name-font);
110
- color: var(--data-card-item-name-color);
110
+ color: var(--data-card-item-name-color, var(--md-sys-color-secondary));
111
111
  }
112
112
 
113
113
  ox-card-field[desc] {
114
114
  font: var(--data-card-item-disc-font);
115
- color: var(--data-card-item-disc-color);
115
+ color: var(--data-card-item-disc-color, var(--md-sys-color-tertiary));
116
116
  }
117
117
  `
118
118
  ]
@@ -545,6 +545,10 @@ export class DataGridBody extends LitElement {
545
545
  const opacity = selectBlock.style.opacity
546
546
 
547
547
  selectBlock.setAttribute('data-tooltip', 'copied to clipboard!')
548
+ const rect = selectBlock.getBoundingClientRect()
549
+ selectBlock.style.setProperty('--tooltip-top', `${rect.top}px`)
550
+ selectBlock.style.setProperty('--tooltip-left', `${rect.left}px`)
551
+
548
552
  selectBlock.style.backgroundColor = 'red'
549
553
  selectBlock.style.opacity = '0.5'
550
554
  await sleep(500)
@@ -3,10 +3,10 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from 'lit'
3
3
  import { customElement, property } from 'lit/decorators.js'
4
4
 
5
5
  import { TooltipStyles } from '@operato/styles'
6
+ import { TooltipReactiveController } from '@operato/utils'
6
7
 
7
8
  import { ZERO_COLUMN } from '../configure/zero-config'
8
9
  import { ColumnConfig, GristRecord } from '../types'
9
- import { getRenderer } from '../renderers'
10
10
 
11
11
  const DEFAULT_TEXT_ALIGN = 'left'
12
12
 
@@ -103,6 +103,8 @@ export class DataGridField extends LitElement {
103
103
  private _onFieldChange: (e: Event) => void = e => {}
104
104
  private _onKeydownInEditingMode: (e: KeyboardEvent) => void = e => {}
105
105
 
106
+ private tooltipController?: TooltipReactiveController = new TooltipReactiveController(this)
107
+
106
108
  render(): TemplateResult {
107
109
  if (!this.column) {
108
110
  return html``
@@ -8,7 +8,7 @@ import throttle from 'lodash-es/throttle'
8
8
 
9
9
  import { OxPopup } from '@operato/popup'
10
10
  import { TooltipStyles } from '@operato/styles'
11
- import { closestElement, detectOverflow } from '@operato/utils'
11
+ import { TooltipReactiveController, closestElement } from '@operato/utils'
12
12
 
13
13
  import { ZERO_COLUMNS, ZERO_DATA } from '../configure/zero-config'
14
14
  import { FilterStyles } from '../filters/filter-styles'
@@ -163,6 +163,8 @@ export class DataGridHeader extends LitElement {
163
163
  private _lastAccVal?: number
164
164
  private _throttledNotifier?: any
165
165
 
166
+ private tooltipController?: TooltipReactiveController = new TooltipReactiveController(this)
167
+
166
168
  connectedCallback() {
167
169
  super.connectedCallback()
168
170
 
@@ -232,19 +234,9 @@ export class DataGridHeader extends LitElement {
232
234
  >
233
235
  <span
234
236
  for-title
237
+ data-reactive-tooltip
235
238
  style="text-align:${align || column.record.align || 'left'};"
236
239
  @click=${(e: MouseEvent) => this._changeSort(column)}
237
- @mouseover=${(e: MouseEvent) => {
238
- const element = e.target as HTMLSpanElement
239
-
240
- if (detectOverflow(element)) {
241
- element.setAttribute('data-tooltip', element.textContent!.trim())
242
- }
243
- }}
244
- @mouseout=${(e: MouseEvent) => {
245
- const element = e.target as HTMLSpanElement
246
- element.removeAttribute('data-tooltip')
247
- }}
248
240
  >${this._renderHeader(column)}
249
241
  </span>
250
242
 
package/src/data-grist.ts CHANGED
@@ -392,7 +392,7 @@ export class DataGrist extends LitElement implements DataConsumer {
392
392
  column =>
393
393
  column.filter &&
394
394
  'value' in (column.filter as FilterConfigObject) &&
395
- (column.filter as FilterConfigObject).value
395
+ (column.filter as FilterConfigObject).value !== undefined
396
396
  )
397
397
  .map(column => {
398
398
  const filter = column.filter as FilterConfigObject
@@ -62,7 +62,7 @@ export class RecordPartial extends LitElement {
62
62
  text-indent: 1px;
63
63
  left: var(--spacing-none);
64
64
  top: var(--spacing-none);
65
- color: var(--grid-record-dirty-color);
65
+ color: var(--grid-record-dirty-color, var(--md-sys-color-error));
66
66
  }
67
67
 
68
68
  [content] {
@@ -88,17 +88,17 @@ export class RecordPartial extends LitElement {
88
88
 
89
89
  ox-list-field {
90
90
  font: var(--data-list-item-etc-font);
91
- color: var(--data-list-item-etc-color);
91
+ color: var(--data-list-item-etc-color, var(--md-sys-color-on-surface));
92
92
  }
93
93
 
94
94
  ox-list-field[name] {
95
95
  font: var(--data-list-item-name-font);
96
- color: var(--data-list-item-name-color);
96
+ color: var(--data-list-item-name-color, var(--md-sys-color-secondary));
97
97
  }
98
98
 
99
99
  ox-list-field[desc] {
100
100
  font: var(--data-list-item-disc-font);
101
- color: var(--data-list-item-disc-color);
101
+ color: var(--data-list-item-disc-color, var(--md-sys-color-tertiary));
102
102
  }
103
103
  `
104
104
  ]
@@ -5,6 +5,6 @@ import { html } from 'lit'
5
5
  @customElement('ox-grist-editor-date')
6
6
  export class OxGristEditorDate extends OxGristEditor {
7
7
  get editorTemplate() {
8
- return html` <input type="date" .value=${this.value} /> `
8
+ return html` <input type="date" .value=${this.value} max="9999-12-31" /> `
9
9
  }
10
10
  }
@@ -22,6 +22,6 @@ export class OxGristEditorDateTime extends OxGristEditor {
22
22
  }
23
23
 
24
24
  get editorTemplate() {
25
- return html` <input type="datetime-local" .value=${this.value} /> `
25
+ return html` <input type="datetime-local" .value=${this.value} max="9999-12-31T23:59" /> `
26
26
  }
27
27
  }
@@ -21,7 +21,7 @@ export const FilterCheckbox: FilterSelectRenderer = (column, value, owner) => {
21
21
  name=${column.name}
22
22
  ?checked=${value}
23
23
  indeterminatable
24
- ?indeterminate=${value == null}
24
+ ?indeterminate=${value === undefined}
25
25
  left-label
26
26
  @change=${(e: CustomEvent) => {
27
27
  ;(e.target as HTMLElement).dispatchEvent(
@@ -107,7 +107,7 @@ export class OxGristFilterPersonalizer extends LitElement {
107
107
  ${this.preference?.filters!.map(
108
108
  filter => html`
109
109
  <ox-checkbox label="checkbox" ?checked=${!filter.hidden} value=${filter.name} option
110
- >${filter.name}<span style="position: absolute; right: 10px; cursor: move;" handle
110
+ >${filter.name}<span style="position: absolute; right: 10px; cursor: row-resize;opacity:.5" handle
111
111
  >☰</span
112
112
  ></ox-checkbox
113
113
  >
@@ -22,10 +22,11 @@ export class OxGristPersonalizer extends LitElement {
22
22
  background-color: var(--ox-grist-p13n-background-color, var(--md-sys-color-primary));
23
23
  border-radius: 0px 0px 7px 7px;
24
24
  cursor: pointer;
25
+ padding-top:var(--spacing-small);
25
26
 
26
27
  &:hover {
27
- color: var(--ox-grist-p13n-hover-color, var(--md-sys-color-primary));
28
- background-color: var(--ox-grist-p13n-hover-background-color, var(--md-sys-color-on-primary));
28
+ color: var(--ox-grist-p13n-hover-color, var(--md-sys-color-on-primary));
29
+ background-color: var(--ox-grist-p13n-hover-background-color, var(--md-sys-color-surface-tint));
29
30
  }
30
31
  }
31
32
  `
@@ -69,8 +70,18 @@ export class OxGristPersonalizer extends LitElement {
69
70
 
70
71
  const template = html`
71
72
  <div class="personalizer-header" slot="header">
72
- <md-icon
73
- style="margin-left: auto;"
73
+ <div
74
+ style=${`
75
+ display: flex;
76
+ align-items: center;
77
+ margin: 0 0 0 auto;
78
+ min-height: 1.4em;
79
+ color: var(--ox-grist-p13n-button-color, var(--md-sys-color-on-primary));
80
+ background-color: var(--ox-grist-p13n-button-background-color, var(--md-sys-color-primary));
81
+ border-radius: var(--md-sys-shape-corner-small);
82
+ padding: 0 var(--spacing-small);
83
+ cursor: pointer;
84
+ `}
74
85
  @click=${async (e: MouseEvent) => {
75
86
  if (grist.personalConfigProvider) {
76
87
  const { mode, columns, sorters, pagination } = this.preference || {}
@@ -83,9 +94,21 @@ export class OxGristPersonalizer extends LitElement {
83
94
  }
84
95
  popup.close()
85
96
  }}
86
- title=${String(i18next.t('button.save'))}
87
- >keep</md-icon
88
- ><md-icon
97
+ >
98
+ ${String(i18next.t('button.save'))}
99
+ </div>
100
+ <div
101
+ style=${`
102
+ display: flex;
103
+ align-items: center;
104
+ margin: 0 0 0 var(--spacing-small, 4px);
105
+ min-height: 1.4em;
106
+ color: var(--ox-grist-p13n-button-color, var(--md-sys-color-on-primary));
107
+ background-color: var(--ox-grist-p13n-button-background-color, var(--md-sys-color-primary));
108
+ border-radius: var(--md-sys-shape-corner-small);
109
+ padding: 0 var(--spacing-small);
110
+ cursor: pointer;
111
+ `}
89
112
  @click=${async (e: MouseEvent) => {
90
113
  if (grist.personalConfigProvider) {
91
114
  grist.personalConfig = this.preference = {}
@@ -93,9 +116,17 @@ export class OxGristPersonalizer extends LitElement {
93
116
  }
94
117
  popup.close()
95
118
  }}
96
- title=${String(i18next.t('button.delete'))}
97
- >keep_off</md-icon
98
- ><md-icon @click=${async (e: MouseEvent) => popup.close()} title=${String(i18next.t('button.close'))}
119
+ >
120
+ ${String(i18next.t('button.delete'))}
121
+ </div>
122
+ <md-icon
123
+ style=${`
124
+ --md-icon-size: 1.2em;
125
+ margin-left: var(--spacing-tiny, 2px);
126
+ cursor: pointer;
127
+ `}
128
+ @click=${async (e: MouseEvent) => popup.close()}
129
+ title=${String(i18next.t('button.close'))}
99
130
  >close</md-icon
100
131
  >
101
132
  </div>
@@ -103,7 +134,7 @@ export class OxGristPersonalizer extends LitElement {
103
134
  ${columns.map(
104
135
  column => html`
105
136
  <ox-checkbox label="checkbox" ?checked=${!column.hidden} value=${column.name} option
106
- >${column.header.renderer(column)}<span style="position: absolute; right: 10px; cursor: move;" handle
137
+ >${column.header.renderer(column)}<span style="position: absolute; right: 10px; cursor: row-resize;opacity:.5" handle
107
138
  >☰</span
108
139
  ></ox-checkbox
109
140
  >
@@ -11,6 +11,10 @@ function onmouseover(e: Event) {
11
11
  } catch {}
12
12
 
13
13
  element.setAttribute('data-tooltip', JSON5.stringify(parsed, null, 2))
14
+
15
+ const rect = element.getBoundingClientRect()
16
+ element.style.setProperty('--tooltip-top', `${rect.top + rect.height}px`)
17
+ element.style.setProperty('--tooltip-left', `${rect.left}px`)
14
18
  }
15
19
 
16
20
  function onmouseout(e: Event) {
@@ -1,21 +1,9 @@
1
1
  import { html } from 'lit'
2
2
 
3
- import { detectOverflow, format as formatter } from '@operato/utils'
3
+ import { format as formatter } from '@operato/utils'
4
4
 
5
5
  import { FieldRenderer } from '../types'
6
6
 
7
- function onmouseover(e: Event) {
8
- const element = e.target as HTMLSpanElement
9
- if (detectOverflow(element)) {
10
- element.setAttribute('data-tooltip', element.textContent!)
11
- }
12
- }
13
-
14
- function onmouseout(e: Event) {
15
- const element = e.target as HTMLSpanElement
16
- element.removeAttribute('data-tooltip')
17
- }
18
-
19
7
  export const OxGristRendererText: FieldRenderer = (value, column, record, rowIndex, field) => {
20
8
  var { format } = column.record || {}
21
9
 
@@ -24,5 +12,5 @@ export const OxGristRendererText: FieldRenderer = (value, column, record, rowInd
24
12
  text = formatter(format, text)
25
13
  }
26
14
 
27
- return html`<span @mouseover=${onmouseover} @mouseout=${onmouseout}>${text}&nbsp;</span>`
15
+ return html`<span data-reactive-tooltip>${text}&nbsp;</span>`
28
16
  }
@@ -28,6 +28,8 @@ const fetchHandler: FetchHandler = async ({ sorters = [], filters, page, limit }
28
28
  number: idx,
29
29
  float: 1.3,
30
30
  date: '2023-09-20',
31
+ active: idx % 2 === 0,
32
+ role: 'admin',
31
33
  routing: {
32
34
  id: '006dc64d-4fb9-4afc-a9ea-962bd1b9e110',
33
35
  name: '조림>세척'
@@ -136,6 +138,40 @@ function buildConfig({ headerFilter }: { headerFilter: boolean }) {
136
138
  header: 'date',
137
139
  width: 120
138
140
  },
141
+ {
142
+ type: 'boolean',
143
+ name: 'active',
144
+ header: 'active',
145
+ record: {
146
+ editable: true
147
+ },
148
+ filter: {
149
+ value: false
150
+ },
151
+ sortable: true,
152
+ width: 60
153
+ },
154
+ {
155
+ type: 'select',
156
+ name: 'role',
157
+ label: true,
158
+ header: 'role',
159
+ record: {
160
+ // options: ['', 'admin', 'worker', 'tester'],
161
+ options: [
162
+ { display: 'admin', value: 'admin' },
163
+ { display: 'worker', value: 'worker' },
164
+ { display: 'tester', value: 'tester' }
165
+ ],
166
+ editable: true
167
+ },
168
+ filter: {
169
+ operator: 'in',
170
+ value: ['worker']
171
+ },
172
+ sortable: true,
173
+ width: 120
174
+ },
139
175
  {
140
176
  type: 'object',
141
177
  name: 'routing',
@@ -413,9 +413,14 @@ const Template: Story<ArgTypes> = ({
413
413
  </style>
414
414
 
415
415
  <style>
416
- ${CommonGristStyles.cssText} ${CommonHeaderStyles.cssText}
416
+ ${CommonGristStyles.cssText}
417
+ ${CommonHeaderStyles.cssText}
417
418
  </style>
418
419
 
420
+ <script>
421
+ document.body.classList.add('${theme}')
422
+ </script>
423
+
419
424
  <style>
420
425
  ox-grist {
421
426
  height: 600px;
@@ -171,3 +171,7 @@ body {
171
171
  --data-card-create-form-padding: 7px;
172
172
  }
173
173
  }
174
+
175
+ body.dark {
176
+ --grid-container-border-color: 1px solid rgba(255, 255, 255, 0.09);
177
+ }
@@ -1,4 +1,7 @@
1
1
  {
2
+ "button.save": "save",
3
+ "button.delete": "delete",
4
+
2
5
  "label.accumulator_sum": "sum",
3
6
  "label.accumulator_avg": "avg",
4
7
  "label.accumulator_count": "cnt",
@@ -1,4 +1,6 @@
1
1
  {
2
+ "button.delete": "削除",
3
+ "button.save": "保存",
2
4
  "label.accumulator_sum": "合計",
3
5
  "label.accumulator_avg": "平均",
4
6
  "label.accumulator_count": "カウント",
@@ -1,4 +1,6 @@
1
1
  {
2
+ "button.delete": "삭제",
3
+ "button.save": "저장",
2
4
  "label.accumulator_sum": "합계",
3
5
  "label.accumulator_avg": "평균",
4
6
  "label.accumulator_count": "계수",
@@ -1,4 +1,6 @@
1
1
  {
2
+ "button.delete": "padam",
3
+ "button.save": "simpan",
2
4
  "label.accumulator_sum": "Jumlah",
3
5
  "label.accumulator_avg": "Purata",
4
6
  "label.accumulator_count": "Kiraan",
@@ -1,4 +1,6 @@
1
1
  {
2
+ "button.delete": "删除",
3
+ "button.save": "保存",
2
4
  "label.accumulator_sum": "总和",
3
5
  "label.accumulator_avg": "平均",
4
6
  "label.accumulator_count": "计数",