@operato/scene-openlayers 1.2.54

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 (66) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +13 -0
  3. package/assets/favicon.ico +0 -0
  4. package/assets/images/spinner.png +0 -0
  5. package/db.sqlite +0 -0
  6. package/dist/editors/index.d.ts +0 -0
  7. package/dist/editors/index.js +2 -0
  8. package/dist/editors/index.js.map +1 -0
  9. package/dist/groups/geography.d.ts +6 -0
  10. package/dist/groups/geography.js +48 -0
  11. package/dist/groups/geography.js.map +1 -0
  12. package/dist/groups/index.d.ts +7 -0
  13. package/dist/groups/index.js +3 -0
  14. package/dist/groups/index.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +3 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/ol-marker.d.ts +79 -0
  19. package/dist/ol-marker.js +247 -0
  20. package/dist/ol-marker.js.map +1 -0
  21. package/dist/openlayers.d.ts +37 -0
  22. package/dist/openlayers.js +211 -0
  23. package/dist/openlayers.js.map +1 -0
  24. package/dist/templates/index.d.ts +14 -0
  25. package/dist/templates/index.js +4 -0
  26. package/dist/templates/index.js.map +1 -0
  27. package/dist/templates/ol-marker copy.d.ts +14 -0
  28. package/dist/templates/ol-marker copy.js +16 -0
  29. package/dist/templates/ol-marker copy.js.map +1 -0
  30. package/dist/templates/ol-marker.d.ts +14 -0
  31. package/dist/templates/ol-marker.js +16 -0
  32. package/dist/templates/ol-marker.js.map +1 -0
  33. package/dist/templates/ol-path.d.ts +14 -0
  34. package/dist/templates/ol-path.js +16 -0
  35. package/dist/templates/ol-path.js.map +1 -0
  36. package/dist/templates/openlayers.d.ts +14 -0
  37. package/dist/templates/openlayers.js +16 -0
  38. package/dist/templates/openlayers.js.map +1 -0
  39. package/dist/tsconfig.tsbuildinfo +1 -0
  40. package/icons/ol-marker-template.png +0 -0
  41. package/icons/ol-path-template.png +0 -0
  42. package/icons/openlayers-template.png +0 -0
  43. package/logs/.08636eb59927f12972f6774f5947c8507b3564c2-audit.json +15 -0
  44. package/logs/.5e5d741d8b7784a2fbad65eedc0fd46946aaf6f2-audit.json +15 -0
  45. package/logs/application-2023-09-02-17.log +15 -0
  46. package/logs/connections-2023-09-02-17.log +76 -0
  47. package/package.json +63 -0
  48. package/schema.gql +3702 -0
  49. package/src/editors/index.ts +0 -0
  50. package/src/groups/geography.ts +48 -0
  51. package/src/groups/index.ts +3 -0
  52. package/src/index.ts +2 -0
  53. package/src/ol-marker.ts +318 -0
  54. package/src/ol-path.ts_x +368 -0
  55. package/src/openlayers.ts +256 -0
  56. package/src/templates/index.ts +4 -0
  57. package/src/templates/ol-marker.ts +16 -0
  58. package/src/templates/ol-path.ts +16 -0
  59. package/src/templates/openlayers.ts +16 -0
  60. package/things-scene.config.js +7 -0
  61. package/translations/en.json +3 -0
  62. package/translations/ko.json +3 -0
  63. package/translations/ms.json +3 -0
  64. package/translations/zh.json +3 -0
  65. package/tsconfig.json +23 -0
  66. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,368 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ */
4
+
5
+ import { Component, RectPath, Shape } from '@hatiolab/things-scene'
6
+
7
+ const NATURE = {
8
+ mutable: false,
9
+ resizable: true,
10
+ rotatable: true,
11
+ properties: [
12
+ {
13
+ type: 'id-input',
14
+ label: 'target-map',
15
+ name: 'targetMap',
16
+ property: {
17
+ component: 'google-map'
18
+ }
19
+ },
20
+ {
21
+ type: 'checkbox',
22
+ label: 'show-path',
23
+ name: 'showPath'
24
+ },
25
+ {
26
+ type: 'checkbox',
27
+ label: 'show-intermediate-markers',
28
+ name: 'showIntermediateMarkers'
29
+ },
30
+ {
31
+ type: 'checkbox',
32
+ label: 'start-end-marker-different-design',
33
+ name: 'startEndMarkerDifferentDesign'
34
+ }
35
+ ],
36
+ 'value-property': 'latlngs'
37
+ // help: 'scene/component/gmap-path'
38
+ }
39
+
40
+ const EMPTY_MARKER_PATH = 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z'
41
+ const END_MARKER_PATH =
42
+ 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z M -2,-30 a 2,2 0 1,1 4,0 2,2 0 1,1 -4,0'
43
+ const START_MARKER_PATH =
44
+ 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z m -3,-34 l 0,8 l 8,-4 l -8,-4 z m -0,-0 l 0,8 l 8,-4 l -8,-4'
45
+
46
+ export default class GMapPath extends RectPath(Shape) {
47
+ _infoWindow: any
48
+ _map: any
49
+
50
+ dispose() {
51
+ this.markers && this.markers.forEach(marker => marker.setMap(null))
52
+
53
+ this.markers = null
54
+ delete this._infoWindow
55
+
56
+ super.dispose()
57
+ }
58
+
59
+ ready() {
60
+ super.ready()
61
+
62
+ if (this.isTemplate()) {
63
+ return
64
+ }
65
+
66
+ this.onchangeTargetMap()
67
+ }
68
+
69
+ get map() {
70
+ return this._map
71
+ }
72
+
73
+ findInfoWindow(type) {
74
+ var eventSetting = (this.state.event && this.state.event[type]) || {}
75
+
76
+ var infoWindow =
77
+ /* event spec v1.0 */ eventSetting.infoWindow ||
78
+ /* event spec v1.1 */ (eventSetting.action == 'infoWindow' && eventSetting.target)
79
+
80
+ if (infoWindow) {
81
+ return this.root.findById(infoWindow)
82
+ }
83
+ }
84
+
85
+ getInfoContent(sceneInfoWindow, index) {
86
+ var tpl = Component.template(sceneInfoWindow.model.frontSideTemplate)
87
+ return (
88
+ `<style>${sceneInfoWindow.model.style}</style>` +
89
+ tpl({
90
+ data: this.data,
91
+ index
92
+ })
93
+ )
94
+ }
95
+
96
+ openInfoWindow(iw, index) {
97
+ var content = this.getInfoContent(iw, index)
98
+
99
+ if (!this.map) return
100
+
101
+ var infoWindow = new google.maps.InfoWindow()
102
+ infoWindow.setContent(content)
103
+ infoWindow.open(this.map, this.markers[index])
104
+
105
+ return infoWindow
106
+ }
107
+
108
+ buildMarkers() {
109
+ if (!this.map) {
110
+ return
111
+ }
112
+
113
+ let {
114
+ latlngs = [],
115
+ fillStyle: fillColor,
116
+ alpha: fillOpacity = 1,
117
+ strokeStyle: strokeColor,
118
+ lineWidth: strokeWeight,
119
+ showIntermediateMarkers = false,
120
+ startEndMarkerDifferentDesign = true,
121
+ showPath = false
122
+ } = this.state
123
+
124
+ if (showIntermediateMarkers) {
125
+ var markers = latlngs.map(({ lat, lng }, index) => {
126
+ if (startEndMarkerDifferentDesign) {
127
+ return new google.maps.Marker({
128
+ position: {
129
+ lat: Number(lat) || 0,
130
+ lng: Number(lng) || 0
131
+ },
132
+ map: this.map,
133
+ icon: {
134
+ path: index == 0 ? START_MARKER_PATH : index + 1 == latlngs.length ? END_MARKER_PATH : EMPTY_MARKER_PATH,
135
+ fillColor,
136
+ fillOpacity,
137
+ strokeColor,
138
+ strokeWeight
139
+ },
140
+ index
141
+ })
142
+ } else {
143
+ return new google.maps.Marker({
144
+ position: {
145
+ lat: Number(lat) || 0,
146
+ lng: Number(lng) || 0
147
+ },
148
+ map: this.map,
149
+ icon: {
150
+ path: EMPTY_MARKER_PATH,
151
+ fillColor,
152
+ fillOpacity,
153
+ strokeColor,
154
+ strokeWeight
155
+ },
156
+ index
157
+ })
158
+ }
159
+ })
160
+ } else {
161
+ var spots =
162
+ latlngs.length > 1 ? [latlngs[0], latlngs[latlngs.length - 1]] : latlngs.length == 1 ? [latlngs[0]] : []
163
+
164
+ var markers = spots.map(({ lat, lng }, index) => {
165
+ if (startEndMarkerDifferentDesign) {
166
+ return new google.maps.Marker({
167
+ position: {
168
+ lat: Number(lat) || 0,
169
+ lng: Number(lng) || 0
170
+ },
171
+ map: this.map,
172
+ icon: {
173
+ path: index == 0 ? START_MARKER_PATH : END_MARKER_PATH,
174
+ fillColor,
175
+ fillOpacity,
176
+ strokeColor,
177
+ strokeWeight
178
+ },
179
+ index
180
+ })
181
+ } else {
182
+ return new google.maps.Marker({
183
+ position: {
184
+ lat: Number(lat) || 0,
185
+ lng: Number(lng) || 0
186
+ },
187
+ map: this.map,
188
+ icon: {
189
+ path: EMPTY_MARKER_PATH,
190
+ fillColor,
191
+ fillOpacity,
192
+ strokeColor,
193
+ strokeWeight
194
+ },
195
+ index
196
+ })
197
+ }
198
+ })
199
+ }
200
+
201
+ if (showPath) {
202
+ this.trackPath = new google.maps.Polyline({
203
+ path: latlngs,
204
+ geodesic: true,
205
+ strokeColor: '#FF0000',
206
+ strokeOpacity: 1,
207
+ strokeWeight: 4,
208
+ map: this.map
209
+ })
210
+ }
211
+
212
+ var infowindows = new Array(markers.length)
213
+
214
+ markers.forEach((marker, index) => {
215
+ marker.addListener('click', e => {
216
+ var iw = this.findInfoWindow('tap')
217
+ iw && this.openInfoWindow(iw, index)
218
+
219
+ this.trigger('click', e.ya)
220
+ })
221
+ marker.addListener('mouseover', () => {
222
+ var iw = this.findInfoWindow('hover')
223
+ if (!iw) return
224
+ infowindows[index] = this.openInfoWindow(iw, index)
225
+ })
226
+ marker.addListener('mouseout', () => {
227
+ var infowindow = infowindows[index]
228
+ infowindow && infowindow.close()
229
+ infowindows[index] = null
230
+ })
231
+ })
232
+
233
+ this.markers = markers
234
+ }
235
+
236
+ set markers(markers) {
237
+ if (this._markers) {
238
+ this._markers.forEach(marker => {
239
+ marker.setMap(null)
240
+ google.maps.event.clearInstanceListeners(marker)
241
+ })
242
+
243
+ delete this._markers
244
+ }
245
+
246
+ this._markers = markers
247
+ }
248
+
249
+ get markers() {
250
+ if (!this._markers) {
251
+ this.buildMarkers()
252
+ }
253
+
254
+ return this._markers
255
+ }
256
+
257
+ get trackPath() {
258
+ return this._trackPath
259
+ }
260
+
261
+ set trackPath(trackPath) {
262
+ if (this.trackPath) {
263
+ this.trackPath.setMap(null)
264
+ }
265
+
266
+ this._trackPath = trackPath
267
+ }
268
+
269
+ render(context) {
270
+ var { top, left, width, height } = this.state
271
+
272
+ context.translate(left, top)
273
+
274
+ // 마커 모양 그리기
275
+ context.beginPath()
276
+
277
+ context.moveTo(width / 2, height * 0.9)
278
+ context.bezierCurveTo(width / 2.3, height * 0.6, 0, height / 2, 0, height / 4)
279
+
280
+ context.ellipse(width / 2, height / 4, width / 2, height / 4, 0, Math.PI * 1, Math.PI * 0)
281
+
282
+ context.bezierCurveTo(width, height / 2, width / 1.7, height * 0.6, width / 2, height * 0.9)
283
+ context.closePath()
284
+
285
+ context.translate(-left, -top)
286
+ }
287
+
288
+ get controls() {}
289
+
290
+ onchangeTargetMap() {
291
+ if (this.targetMap) {
292
+ this._targetMap = null
293
+ this._map = null
294
+ }
295
+
296
+ var id = this.get('targetMap')
297
+ if (id !== undefined) {
298
+ this._targetMap = this.root.findById(id)
299
+
300
+ if (this.targetMap) {
301
+ this._map = this.targetMap.map
302
+
303
+ if (!this.map) {
304
+ var listener = after => {
305
+ if ('map' in after) {
306
+ this._map = after.map
307
+ this.markers && this.markers.forEach(marker => marker.setMap(this.map))
308
+
309
+ this.targetMap.off('change', listener)
310
+ }
311
+ }
312
+ this.targetMap.on('change', listener)
313
+ } else {
314
+ this.markers && this.markers.forEach(marker => marker.setMap(this.map))
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ get targetMap() {
321
+ return this._targetMap
322
+ }
323
+
324
+ onchange(after, before) {
325
+ if ('targetMap' in after) {
326
+ this.onchangeTargetMap()
327
+ }
328
+
329
+ if ('latlngs' in after) {
330
+ this.buildMarkers()
331
+ }
332
+
333
+ if (('fillStyle' in after || 'strokeStyle' in after || 'lineWidth' in after) && this.marker) {
334
+ let {
335
+ fillStyle: fillColor,
336
+ alpha: fillOpacity = 1,
337
+ strokeStyle: strokeColor,
338
+ lineWidth: strokeWeight
339
+ } = this.state
340
+
341
+ this.marker.setIcon({
342
+ path: MARKER_PATH,
343
+ fillColor,
344
+ fillOpacity,
345
+ strokeColor,
346
+ strokeWeight
347
+ })
348
+ }
349
+
350
+ super.onchange && super.onchange(after, before)
351
+ }
352
+
353
+ get latlngs() {
354
+ return this.getState('latlngs')
355
+ }
356
+
357
+ set latlngs(latlngs) {
358
+ this.setState({
359
+ latlngs
360
+ })
361
+ }
362
+
363
+ get nature() {
364
+ return NATURE
365
+ }
366
+ }
367
+
368
+ Component.register('gmap-path', GMapPath)
@@ -0,0 +1,256 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ */
4
+
5
+ // @ts-ignore
6
+ import OpenLayersStyle from '!!text-loader!ol/ol.css'
7
+
8
+ import { Feature, Map, View } from 'ol'
9
+ import { Circle as CircleStyle, Fill, Icon, Stroke, Style } from 'ol/style.js'
10
+ import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
11
+ import { Vector as VectorSource, OSM } from 'ol/source'
12
+ import { fromLonLat } from 'ol/proj'
13
+ import { Geometry, Point } from 'ol/geom'
14
+
15
+ import { Component, HTMLOverlayContainer, Properties, ComponentNature, error } from '@hatiolab/things-scene'
16
+
17
+ const MARKER_PATH =
18
+ 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z M -2,-30 a 2,2 0 1,1 4,0 2,2 0 1,1 -4,0'
19
+
20
+ const NATURE: ComponentNature = {
21
+ mutable: false,
22
+ resizable: true,
23
+ rotatable: true,
24
+ properties: [
25
+ {
26
+ type: 'number',
27
+ label: 'latitude',
28
+ name: 'lat',
29
+ property: {
30
+ step: 0.000001,
31
+ max: 90,
32
+ min: -90
33
+ }
34
+ },
35
+ {
36
+ type: 'number',
37
+ label: 'longitude',
38
+ name: 'lng',
39
+ property: {
40
+ step: 0.000001,
41
+ min: -180,
42
+ max: 180
43
+ }
44
+ },
45
+ {
46
+ type: 'number',
47
+ label: 'zoom',
48
+ name: 'zoom'
49
+ },
50
+ {
51
+ type: 'boolean',
52
+ label: 'show-marker',
53
+ name: 'showMarker'
54
+ }
55
+ ],
56
+ 'value-property': 'latlng',
57
+ help: 'scene/component/openlayers'
58
+ }
59
+
60
+ function getGlobalScale(component: Component) {
61
+ var scale = { x: 1, y: 1 }
62
+ var parent = component
63
+
64
+ while (parent) {
65
+ let { x, y } = parent.get('scale') || { x: 1, y: 1 }
66
+ scale.x *= x || 1
67
+ scale.y *= y || 1
68
+
69
+ parent = parent.parent
70
+ }
71
+ return scale
72
+ }
73
+
74
+ export default class Openlayers extends HTMLOverlayContainer {
75
+ static markerStyle: Style = new Style({
76
+ image: new Icon({
77
+ src:
78
+ 'data:image/svg+xml;charset=utf-8,' +
79
+ encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">' + MARKER_PATH + '</svg>'),
80
+ anchor: [0.5, 1]
81
+ })
82
+ })
83
+
84
+ _anchor?: HTMLDivElement
85
+ _map: Map | null = null
86
+ _listenTo?: Component
87
+ _listener?: Function
88
+ _vectorSource?: VectorSource<Geometry>
89
+
90
+ get eventMap() {
91
+ return {
92
+ 'model-layer': {
93
+ '(self)': {
94
+ change: (after: any) => {
95
+ after.scale && this.rescale()
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ /*
103
+ * 부모의 스케일의 역으로 transform해서, scale을 1로 맞추어준다.
104
+ */
105
+ rescale() {
106
+ var anchor = this._anchor
107
+ if (!anchor) {
108
+ return
109
+ }
110
+
111
+ var scale = getGlobalScale(this)
112
+
113
+ var sx = 1 / scale.x
114
+ var sy = 1 / scale.y
115
+
116
+ var transform = `scale(${sx}, ${sy})`
117
+
118
+ anchor!.style.transform = transform
119
+ anchor!.style.transformOrigin = '0px 0px'
120
+
121
+ var { width, height } = this.state
122
+ anchor.style.width = width * scale.x + 'px'
123
+ anchor.style.height = height * scale.y + 'px'
124
+ }
125
+
126
+ createElement() {
127
+ super.createElement()
128
+ this._anchor = document.createElement('div')
129
+
130
+ const style = document.createElement('style')
131
+ style.textContent = `
132
+ ${OpenLayersStyle}
133
+ `
134
+
135
+ this.element.appendChild(style)
136
+ this.element.appendChild(this._anchor)
137
+
138
+ const { lat, lng, zoom, showMarker } = this.state
139
+
140
+ // 지도의 중심 좌표
141
+ const center = fromLonLat([lng || 126.9783882, lat || 37.5666103])
142
+
143
+ // 타일 레이어 생성 (배경 지도)
144
+ const tileLayer = new TileLayer({
145
+ source: new OSM({
146
+ attributions: ''
147
+ })
148
+ })
149
+
150
+ // 벡터 레이어 생성
151
+ const styles: { [name: string]: Style } = {
152
+ route: new Style({
153
+ stroke: new Stroke({
154
+ width: 6,
155
+ color: [237, 212, 0, 0.8]
156
+ })
157
+ }),
158
+ marker: Openlayers.markerStyle,
159
+ circle: new Style({
160
+ image: new CircleStyle({
161
+ radius: 7,
162
+ stroke: new Stroke({
163
+ color: 'black',
164
+ width: 2
165
+ })
166
+ })
167
+ })
168
+ }
169
+
170
+ const vectorSource = new VectorSource()
171
+ const vectorLayer = new VectorLayer({
172
+ source: vectorSource,
173
+ style: function (feature) {
174
+ return styles[feature.get('type')]
175
+ }
176
+ })
177
+
178
+ // 지도 생성
179
+ const map = new Map({
180
+ target: this._anchor,
181
+ layers: [tileLayer, vectorLayer],
182
+ view: new View({
183
+ center,
184
+ zoom
185
+ })
186
+ })
187
+
188
+ this._map = map
189
+ this._vectorSource = vectorSource
190
+
191
+ if (showMarker) {
192
+ const marker = new Feature({
193
+ type: 'circle',
194
+ geometry: new Point(center)
195
+ })
196
+
197
+ this._vectorSource.addFeatures([marker])
198
+ }
199
+
200
+ this.rescale()
201
+ }
202
+
203
+ get tagName() {
204
+ return 'div'
205
+ }
206
+
207
+ get map() {
208
+ return this._map
209
+ }
210
+
211
+ dispose() {
212
+ super.dispose()
213
+
214
+ delete this._anchor
215
+ }
216
+
217
+ setElementProperties(div: HTMLDivElement) {
218
+ this.rescale()
219
+ }
220
+
221
+ onchange(after: Properties, before: Properties) {
222
+ if (after.zoom) {
223
+ const view = this.map?.getView()
224
+ view?.setCenter(after.zoom)
225
+ }
226
+
227
+ if ('lat' in after || 'lng' in after) {
228
+ let { lat, lng } = this.state
229
+ const view = this.map?.getView()
230
+ view?.setCenter(fromLonLat([lng, lat]))
231
+ }
232
+
233
+ super.onchange(after, before)
234
+
235
+ this.rescale()
236
+ }
237
+
238
+ get latlng() {
239
+ const { lat, lng } = this.state
240
+ return { lat, lng }
241
+ }
242
+
243
+ set latlng(latlng) {
244
+ this.setState(latlng)
245
+ }
246
+
247
+ get vectorSource() {
248
+ return this._vectorSource
249
+ }
250
+
251
+ get nature() {
252
+ return NATURE
253
+ }
254
+ }
255
+
256
+ Component.register('openlayers', Openlayers)
@@ -0,0 +1,4 @@
1
+ import openlayers from './openlayers'
2
+ import olMarker from './ol-marker'
3
+
4
+ export default [openlayers, olMarker]
@@ -0,0 +1,16 @@
1
+ const icon = new URL('../../icons/ol-marker-template.png', import.meta.url).href
2
+
3
+ export default {
4
+ type: 'ol-marker',
5
+ description: 'ol-marker',
6
+ // group: 'geographic',
7
+ group: 'etc',
8
+ icon,
9
+ model: {
10
+ type: 'ol-marker',
11
+ left: 10,
12
+ top: 10,
13
+ width: 55,
14
+ height: 100
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ const icon = new URL('../../icons/ol-path-template.png', import.meta.url).href
2
+
3
+ export default {
4
+ type: 'ol-path',
5
+ description: 'ol-path',
6
+ // group: 'geographic',
7
+ group: 'etc',
8
+ icon,
9
+ model: {
10
+ type: 'ol-path',
11
+ left: 10,
12
+ top: 10,
13
+ width: 100,
14
+ height: 20
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ const icon = new URL('../../icons/openlayers-template.png', import.meta.url).href
2
+
3
+ export default {
4
+ type: 'openlayers',
5
+ description: 'openlayers',
6
+ // group: 'geographic',
7
+ group: 'etc',
8
+ icon,
9
+ model: {
10
+ type: 'openlayers',
11
+ left: 10,
12
+ top: 10,
13
+ width: 100,
14
+ height: 20
15
+ }
16
+ }
@@ -0,0 +1,7 @@
1
+ import groups from './dist/groups'
2
+ import templates from './dist/templates'
3
+
4
+ export default {
5
+ templates,
6
+ groups
7
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "keyword": "keyword"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "keyword": "키워드"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "keyword": "[ms] keyword"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "keyword": "关键词"
3
+ }