@operato/board 10.29.8 → 10.29.10

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": "@operato/board",
3
- "version": "10.29.8",
3
+ "version": "10.29.10",
4
4
  "description": "Webcomponent for board following open-wc recommendations",
5
5
  "author": "heartyoh",
6
6
  "type": "module",
@@ -120,18 +120,18 @@
120
120
  "dependencies": {
121
121
  "@material/web": "^2.0.0",
122
122
  "@open-wc/scoped-elements": "^2.1.3",
123
- "@operato/app": "^10.29.8",
124
- "@operato/data-grist": "^10.29.8",
125
- "@operato/font": "^10.29.8",
123
+ "@operato/app": "^10.29.9",
124
+ "@operato/data-grist": "^10.29.9",
125
+ "@operato/font": "^10.29.9",
126
126
  "@operato/graphql": "^10.29.5",
127
127
  "@operato/i18n": "^10.29.1",
128
- "@operato/input": "^10.29.1",
129
- "@operato/layout": "^10.29.7",
130
- "@operato/markdown": "^10.29.1",
131
- "@operato/popup": "^10.29.1",
132
- "@operato/property-editor": "^10.29.8",
133
- "@operato/property-panel": "^10.29.8",
134
- "@operato/styles": "^10.29.1",
128
+ "@operato/input": "^10.29.9",
129
+ "@operato/layout": "^10.29.9",
130
+ "@operato/markdown": "^10.29.9",
131
+ "@operato/popup": "^10.29.9",
132
+ "@operato/property-editor": "^10.29.9",
133
+ "@operato/property-panel": "^10.29.9",
134
+ "@operato/styles": "^10.29.9",
135
135
  "@operato/utils": "^10.29.1",
136
136
  "@types/file-saver": "^2.0.4",
137
137
  "@types/sortablejs": "^1.10.7",
@@ -144,7 +144,7 @@
144
144
  "devDependencies": {
145
145
  "@custom-elements-manifest/analyzer": "^0.10.0",
146
146
  "@hatiolab/prettier-config": "^1.0.0",
147
- "@hatiolab/things-scene": "^10.1.61",
147
+ "@hatiolab/things-scene": "^10.1.74",
148
148
  "@open-wc/eslint-config": "^12.0.3",
149
149
  "@open-wc/testing": "^4.0.0",
150
150
  "@rollup/plugin-image": "^3.0.3",
@@ -180,5 +180,5 @@
180
180
  "prettier --write"
181
181
  ]
182
182
  },
183
- "gitHead": "7afbba99cf2119ccb709b6cb0cff8b58d447ef88"
183
+ "gitHead": "d9e090a2bbdaa75eff2e8864f1557fbfbc814733"
184
184
  }
@@ -0,0 +1,69 @@
1
+ import { expect, fixture, html } from '@open-wc/testing'
2
+
3
+ import { ComponentMenu } from '../src/modeller/component-toolbar/component-menu.js'
4
+
5
+ /**
6
+ * A tray template that cannot be placed here (ADR-0114 P7): shown dimmed with its mark, its reason on hover, and
7
+ * nothing happens when it is picked -- no add mode, no image drag.
8
+ */
9
+ customElements.get('test-component-menu') || customElements.define('test-component-menu', class extends ComponentMenu {})
10
+
11
+ const group = {
12
+ name: 'figure',
13
+ templates: [
14
+ { type: 'hatiolab.com/ok', description: 'OK figure', icon: '', group: 'figure', model: { type: 'hatiolab.com/ok' } },
15
+ {
16
+ type: 'hatiolab.com/future',
17
+ description: 'Future figure',
18
+ icon: '',
19
+ group: 'figure',
20
+ model: { type: 'hatiolab.com/future' },
21
+ unavailableReason: 'this server cannot read figure-asset-9',
22
+ unavailableLabel: 'format unsupported'
23
+ }
24
+ ]
25
+ }
26
+
27
+ async function menuWith(added: unknown[]) {
28
+ const scene = { rootContainer: { trigger: () => {} }, startAddMode: (model: unknown) => added.push(model) }
29
+ const menu = (await fixture(html`<test-component-menu .groups=${[group]} .scene=${scene as never}></test-component-menu>`)) as ComponentMenu
30
+ menu.group = 'figure'
31
+ await menu.updateComplete
32
+ return menu
33
+ }
34
+
35
+ const itemOf = (menu: ComponentMenu, type: string) => menu.renderRoot.querySelector(`[data-type="${type}"]`) as HTMLElement
36
+
37
+ describe('트레이 — 놓을 수 없는 템플릿', () => {
38
+ it('흐리게 · 표 · 사유를 가진다, 아이콘은 끌리지 않는다', async () => {
39
+ const menu = await menuWith([])
40
+ const item = itemOf(menu, 'hatiolab.com/future')
41
+ expect(item.hasAttribute('unavailable')).to.equal(true)
42
+ expect(item.getAttribute('aria-disabled')).to.equal('true')
43
+ expect(item.getAttribute('title')).to.equal('this server cannot read figure-asset-9')
44
+ expect(item.querySelector('[mark]')!.textContent).to.equal('format unsupported')
45
+ expect(item.querySelector('[reason]')!.textContent).to.equal('this server cannot read figure-asset-9')
46
+ expect(item.querySelector('img')!.getAttribute('draggable')).to.equal('false')
47
+ expect(getComputedStyle(item).opacity).to.equal('0.5')
48
+ })
49
+
50
+ it('★ 고르면 아무 일도 없다 — 놓을 수 있는 것은 추가 모드에 들어간다', async () => {
51
+ const added: unknown[] = []
52
+ const menu = await menuWith(added)
53
+
54
+ itemOf(menu, 'hatiolab.com/future').click()
55
+ expect(added).to.deep.equal([])
56
+
57
+ menu.group = 'figure'
58
+ await menu.updateComplete
59
+ itemOf(menu, 'hatiolab.com/ok').click()
60
+ expect(added).to.deep.equal([{ type: 'hatiolab.com/ok' }])
61
+ })
62
+
63
+ it('놓을 수 있는 템플릿에는 표 · 사유가 없다', async () => {
64
+ const menu = await menuWith([])
65
+ const item = itemOf(menu, 'hatiolab.com/ok')
66
+ expect(item.hasAttribute('unavailable')).to.equal(false)
67
+ expect(item.querySelector('[mark]')).to.equal(null)
68
+ })
69
+ })
@@ -2,10 +2,17 @@ import { expect } from '@open-wc/testing'
2
2
 
3
3
  import { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene'
4
4
 
5
- import { createFigureResolver } from '../src/figure-provider.js'
5
+ import {
6
+ createFigureResolver,
7
+ FIGURE_TYPE_MAP_CHANGED,
8
+ figureMapReleaseOfPage,
9
+ resetFigureMapPage,
10
+ setFigureMapChoice,
11
+ type FigureBlueprintsAnswer
12
+ } from '../src/figure-provider.js'
6
13
 
7
14
  /**
8
- * The path a published figure takes to a board (ADR-0063 결정 2).
15
+ * The path a figure takes to a board (ADR-0063 결정 2, ADR-0114): one batch question by full name.
9
16
  *
10
17
  * things-scene holds the seat and knows nothing about figures; this package fills it. What is
11
18
  * checked here is the part that belongs to the board: which names are asked about, what happens to
@@ -33,11 +40,17 @@ const figureAssetOf = (type: string) => ({
33
40
  { id: 'a0', unit: 'deg', value: 0 }
34
41
  ],
35
42
  nodes: [
36
- { id: 'body.shape', op: 'box-shape@1', args: ['w', 'w', 'w'], outputs: { shape: 'body.s' }, params: { frame: 'body.local' } },
43
+ {
44
+ id: 'body.shape',
45
+ op: 'extrude@1',
46
+ args: ['zero', 'w', 'w', 'w'],
47
+ outputs: { shape: 'body.s' },
48
+ params: { frame: 'body.local', axis: 'y', section: 'rectangle' }
49
+ },
37
50
  {
38
51
  id: 'body.pose',
39
52
  op: 'rigid@1',
40
- args: ['zero', 'half', 'zero', 'a0', 'a0', 'a0'],
53
+ args: ['zero', 'zero', 'zero', 'a0', 'a0', 'a0'],
41
54
  outputs: { pose: 'body.p' },
42
55
  params: { from: 'body.local', to: 'asset' }
43
56
  },
@@ -56,166 +69,169 @@ const figureAssetOf = (type: string) => ({
56
69
  }
57
70
  })
58
71
 
59
- const figurePayloadOf = (type: string) => ({ type, asset: figureAssetOf(type) })
60
-
61
- /** Records what was asked for, and answers from a table. */
62
- function fetcherOf(published: Record<string, unknown>, failing: string[] = []) {
63
- const asked: string[] = []
64
-
72
+ /** A server that answers from a table, from one map release, and records each question. */
73
+ function serverOf(
74
+ published: Record<string, unknown>,
75
+ release: { version: number; revision: number } | null = { version: 1, revision: 1 },
76
+ refusals: Record<string, string> = {}
77
+ ) {
78
+ const asked: { types: string[]; map: string }[] = []
79
+ const state = { release }
65
80
  return {
66
81
  asked,
67
- fetch: async (type: string) => {
68
- asked.push(type)
69
- if (failing.includes(type)) {
70
- throw new Error(`no route to the door (${type})`)
71
- }
72
- return (published[type] ?? null) as never
82
+ state,
83
+ fetch: async (types: readonly string[], map: string): Promise<FigureBlueprintsAnswer> => {
84
+ asked.push({ types: [...types], map })
85
+ const items = types.filter(type => published[type]).map(type => ({ type, ref: `${type}@1`, asset: published[type] }))
86
+ const refused = types
87
+ .filter(type => !published[type])
88
+ .map(type => ({ type, code: 'figure-not-in-type-map', message: refusals[type] ?? `${type} is not in the type map` }))
89
+ return { release: state.release, items, refused }
73
90
  }
74
91
  }
75
92
  }
76
93
 
77
- describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () => {
94
+ const name = (label: string) => `hatiolab.com/${label}-${Math.random().toString(36).slice(2, 8)}`
95
+
96
+ describe('figure 공급자 — 보드가 Figure 를 정식 이름으로 한 번에 묻는다', () => {
78
97
  afterEach(() => {
79
98
  resetFigures()
99
+ resetFigureMapPage()
80
100
  })
81
101
 
82
- it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {
83
- const { asked, fetch } = fetcherOf({ EQ_MIXER: figurePayloadOf('EQ_MIXER') })
102
+ it('등록되지 않은 type 을 대소문자와 관계없이 한 번에 묻는다 — 이미 등록된 것은 묻지 않는다', async () => {
103
+ const mixer = name('eq-mixer')
104
+ const { asked, fetch } = serverOf({ [mixer]: figureAssetOf(mixer) })
84
105
 
85
- await createFigureResolver(fetch)(['EQ_MIXER', 'rect', 'Conveyor', 'text', 'VEH_AGV_01'])
106
+ await createFigureResolver(fetch)([mixer, 'rect', 'Conveyor', 'text', 'VEH_AGV_01'])
86
107
 
87
- expect(asked).to.deep.equal(['EQ_MIXER', 'VEH_AGV_01'])
108
+ expect(asked).to.deep.equal([{ types: [mixer, 'Conveyor', 'VEH_AGV_01'], map: 'ACTIVE' }])
88
109
  })
89
110
 
90
- it('이미 등록된 type 은 묻지 않는다 — 그리고 있는 컴포넌트를 figure 가 덮지 않는다', async () => {
91
- const { asked, fetch } = fetcherOf({ EQ_TAKEN: figurePayloadOf('EQ_TAKEN') })
92
-
93
- /* Somebody else already draws this name — a plugin, an app's own component. */
94
- Component.register('EQ_TAKEN', class Taken extends Component {} as never)
111
+ it('이미 등록된 이름은 묻지 않고, figure 가 그 컴포넌트를 덮지 않는다', async () => {
112
+ const taken = name('taken')
113
+ const { asked, fetch } = serverOf({ [taken]: figureAssetOf(taken) })
114
+ Component.register(taken, class Taken extends Component {} as never)
95
115
 
96
- await createFigureResolver(fetch)(['EQ_TAKEN'])
116
+ await createFigureResolver(fetch)([taken])
97
117
 
98
118
  expect(asked).to.deep.equal([])
99
- expect(hasFigure('EQ_TAKEN')).to.equal(false)
119
+ expect(hasFigure(taken)).to.equal(false)
100
120
  })
101
121
 
102
- it('받은 자산을 등록한다 — 도면은 type 만 적으면 된다', async () => {
103
- const { fetch } = fetcherOf({ EQ_REGISTERED: figurePayloadOf('EQ_REGISTERED') })
122
+ it('받은 자산을 물은 이름으로 등록한다', async () => {
123
+ const conveyor = name('conveyor')
124
+ const { fetch } = serverOf({ [conveyor]: figureAssetOf(conveyor) })
104
125
 
105
- await createFigureResolver(fetch)(['EQ_REGISTERED'])
126
+ await createFigureResolver(fetch)([conveyor])
106
127
 
107
- expect(hasFigure('EQ_REGISTERED')).to.equal(true)
108
- /* The component type is registered with it — the model writes `type` and nothing else. */
109
- expect(Component.register('EQ_REGISTERED')).to.not.equal(undefined)
128
+ expect(hasFigure(conveyor)).to.equal(true)
129
+ expect(Component.register(conveyor)).to.not.equal(undefined)
110
130
  })
111
131
 
112
- it('자산은 씬의 문으로 등록한다 — 보드는 그것을 읽지 않고 씬에 넘긴다', async () => {
113
- const { fetch } = fetcherOf({ EQ: figurePayloadOf('EQ') })
132
+ it('★ 그리지 못한 이름은 서버가 준 이유를 그대로 돌려준다 — 읽을 수 없는 자산도 그 이름만 이유를 받는다', async () => {
133
+ const good = name('good')
134
+ const broken = name('broken')
135
+ const missing = name('missing')
136
+ const brokenAsset = figureAssetOf(broken)
137
+ brokenAsset.appearance = []
138
+ const { fetch } = serverOf({ [good]: figureAssetOf(good), [broken]: brokenAsset }, { version: 1, revision: 1 }, { [missing]: '타입 맵에 없습니다' })
114
139
 
115
- await createFigureResolver(fetch)(['EQ'])
140
+ const answer = await createFigureResolver(fetch)([good, broken, missing])
116
141
 
117
- expect(hasFigure('EQ')).to.equal(true)
118
- /* 도면은 type 하나만 적으면 그려진다. */
119
- expect(Component.register('EQ')).to.not.equal(undefined)
142
+ expect(hasFigure(good)).to.equal(true)
143
+ expect(hasFigure(broken)).to.equal(false)
144
+ expect(answer!.refused.map(r => r.type)).to.deep.equal([missing, broken])
145
+ expect(answer!.refused[0].message).to.equal('타입 맵에 없습니다')
120
146
  })
121
147
 
122
- it('자산이 없는 답은 그 type 만 실패로 남는다 — 옛 청사진을 그리지 않는다', async () => {
123
- const { fetch } = fetcherOf({ EQ_NEW: figurePayloadOf('EQ_NEW'), EQ_OLD: { version: 1, type: 'EQ_OLD', groups: [] } })
124
-
125
- let message = ''
126
- try {
127
- await createFigureResolver(fetch)(['EQ_NEW', 'EQ_OLD'])
128
- } catch (error) {
129
- message = error instanceof Error ? error.message : String(error)
148
+ it('물을 수 없으면 거절한다 — 씬은 실패로 모으고 보드는 열린다', async () => {
149
+ const fetch = async () => {
150
+ throw new Error('no route to the door')
130
151
  }
131
-
132
- expect(message).to.contain('not a figure asset: EQ_OLD')
133
- expect(hasFigure('EQ_NEW')).to.equal(true)
134
- expect(hasFigure('EQ_OLD')).to.equal(false)
135
- })
136
-
137
- it('읽을 수 없는 자산은 그 type 만 실패로 남는다', async () => {
138
- const broken = figurePayloadOf('EQ_ASSET_BROKEN')
139
- broken.asset.appearance = []
140
- const { fetch } = fetcherOf({ EQ_BESIDE: figurePayloadOf('EQ_BESIDE'), EQ_ASSET_BROKEN: broken })
141
-
142
152
  let message = ''
143
153
  try {
144
- await createFigureResolver(fetch)(['EQ_BESIDE', 'EQ_ASSET_BROKEN'])
154
+ await createFigureResolver(fetch as never)([name('x')])
145
155
  } catch (error) {
146
156
  message = error instanceof Error ? error.message : String(error)
147
157
  }
148
-
149
- expect(message).to.contain('EQ_ASSET_BROKEN')
150
- expect(hasFigure('EQ_BESIDE')).to.equal(true)
151
- expect(hasFigure('EQ_ASSET_BROKEN')).to.equal(false)
158
+ expect(message).to.contain('no route to the door')
152
159
  })
153
160
 
154
- it('발행된 것이 없으면 조용히 자리표시로 남는다 — 거절이 아니다', async () => {
155
- const { fetch } = fetcherOf({})
161
+ it('★ 페이지는 처음 받은 맵 릴리스를 기억하고, 후보의 revision 이 바뀐 답은 등록하지 않는다', async () => {
162
+ const first = name('first')
163
+ const later = name('later')
164
+ const server = serverOf({ [first]: figureAssetOf(first), [later]: figureAssetOf(later) }, { version: 2, revision: 3 })
165
+ const resolver = createFigureResolver(server.fetch)
166
+ setFigureMapChoice('CANDIDATE')
156
167
 
157
- let rejected = false
168
+ await resolver([first])
169
+ expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 })
170
+
171
+ let changed: any = null
172
+ const listen = (e: Event) => (changed = (e as CustomEvent).detail)
173
+ window.addEventListener(FIGURE_TYPE_MAP_CHANGED, listen)
158
174
  try {
159
- await createFigureResolver(fetch)(['EQ_NOT_PUBLISHED'])
160
- } catch {
161
- rejected = true
175
+ server.state.release = { version: 2, revision: 4 }
176
+ const answer = await resolver([later])
177
+ expect(hasFigure(later)).to.equal(false)
178
+ expect(answer!.refused.map(r => r.type)).to.deep.equal([later])
179
+ expect(changed).to.deep.equal({ was: { map: 'CANDIDATE', version: 2, revision: 3 }, now: { map: 'CANDIDATE', version: 2, revision: 4 } })
180
+ expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 })
181
+ } finally {
182
+ window.removeEventListener(FIGURE_TYPE_MAP_CHANGED, listen)
162
183
  }
163
-
164
- expect(rejected).to.equal(false)
165
- expect(hasFigure('EQ_NOT_PUBLISHED')).to.equal(false)
166
184
  })
167
185
 
168
- it('물을 수 없으면 거절한다. 그때도 답을 받은 것은 등록된다', async () => {
169
- const { fetch } = fetcherOf({ EQ_BESIDE_BROKEN: figurePayloadOf('EQ_BESIDE_BROKEN') }, ['EQ_BROKEN'])
186
+ it('★ 활성 맵과 후보 맵의 등록을 한 페이지에서 섞지 않는다', async () => {
187
+ const active = name('active')
188
+ const preview = name('preview')
189
+ const server = serverOf({ [active]: figureAssetOf(active), [preview]: figureAssetOf(preview) }, { version: 1, revision: 1 })
190
+ const resolver = createFigureResolver(server.fetch)
170
191
 
171
- let message = ''
172
- try {
173
- await createFigureResolver(fetch)(['EQ_BESIDE_BROKEN', 'EQ_BROKEN'])
174
- } catch (error) {
175
- message = error instanceof Error ? error.message : String(error)
176
- }
192
+ await resolver([active])
193
+ expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'ACTIVE', version: 1, revision: 1 })
177
194
 
178
- expect(message).to.contain('EQ_BROKEN')
179
- expect(message).to.not.contain('EQ_BESIDE_BROKEN')
180
- expect(hasFigure('EQ_BESIDE_BROKEN')).to.equal(true)
195
+ setFigureMapChoice('CANDIDATE')
196
+ const answer = await resolver([preview])
197
+
198
+ expect(server.asked.map(a => a.map)).to.deep.equal(['ACTIVE', 'CANDIDATE'])
199
+ expect(hasFigure(preview)).to.equal(false)
200
+ expect(answer!.refused.map(r => r.type)).to.deep.equal([preview])
181
201
  })
182
202
 
183
- it('읽을 수 없는 자산은 그 type 만 실패로 남는다 — 다른 type 을 막지 않는다', async () => {
184
- const { fetch } = fetcherOf({
185
- EQ_BESIDE_MALFORMED: figurePayloadOf('EQ_BESIDE_MALFORMED'),
186
- EQ_MALFORMED: { type: 'EQ_MALFORMED', asset: { version: 'figure-asset-1' } }
187
- })
203
+ it('맵 릴리스가 없는 서버(저작 서버의 최신 정책)는 기억하지 않는다', async () => {
204
+ const a = name('a')
205
+ const b = name('b')
206
+ const server = serverOf({ [a]: figureAssetOf(a), [b]: figureAssetOf(b) }, null)
207
+ const resolver = createFigureResolver(server.fetch)
188
208
 
189
- let message = ''
190
- try {
191
- await createFigureResolver(fetch)(['EQ_BESIDE_MALFORMED', 'EQ_MALFORMED'])
192
- } catch (error) {
193
- message = error instanceof Error ? error.message : String(error)
194
- }
209
+ await resolver([a])
210
+ await resolver([b])
195
211
 
196
- expect(message).to.contain('EQ_MALFORMED')
197
- expect(hasFigure('EQ_BESIDE_MALFORMED')).to.equal(true)
198
- expect(hasFigure('EQ_MALFORMED')).to.equal(false)
212
+ expect(figureMapReleaseOfPage()).to.equal(null)
213
+ expect(hasFigure(a)).to.equal(true)
214
+ expect(hasFigure(b)).to.equal(true)
199
215
  })
200
216
 
201
- it('씬의 자리에 앉아 동작한다 — 모델의 모르는 type 이 해석 뒤에 남지 않는다', async () => {
202
- const { fetch } = fetcherOf({ EQ_ON_A_BOARD: figurePayloadOf('EQ_ON_A_BOARD') })
203
- const takeBack = registerTypeResolver(createFigureResolver(fetch))
217
+ it('씬의 자리에 앉아 동작한다 — 받은 것은 그려지고 나머지는 자리표시로 남는다', async () => {
218
+ const onBoard = name('on-board')
219
+ const never = name('never')
220
+ const { fetch } = serverOf({ [onBoard]: figureAssetOf(onBoard) })
221
+ const takeBack = registerTypeResolver(createFigureResolver(fetch) as never)
204
222
 
205
223
  try {
206
224
  const model = {
207
225
  type: 'container',
208
226
  components: [
209
- { type: 'EQ_ON_A_BOARD', left: 0, top: 0, width: 100, height: 100 },
210
- { type: 'EQ_NEVER_PUBLISHED', left: 0, top: 0, width: 100, height: 100 }
227
+ { type: onBoard, left: 0, top: 0, width: 100, height: 100 },
228
+ { type: never, left: 0, top: 0, width: 100, height: 100 }
211
229
  ]
212
230
  }
213
-
214
231
  const resolution = await resolveUnknownTypes(model)
215
-
216
- expect(resolution.unresolved).to.deep.equal(['EQ_NEVER_PUBLISHED'])
232
+ expect(resolution.unresolved).to.deep.equal([never])
217
233
  expect(resolution.failed).to.deep.equal([])
218
- expect(hasFigure('EQ_ON_A_BOARD')).to.equal(true)
234
+ expect(hasFigure(onBoard)).to.equal(true)
219
235
  } finally {
220
236
  takeBack()
221
237
  }
@@ -36,5 +36,6 @@
36
36
  "label.list": "list",
37
37
  "label.visibility-domain": "public in group",
38
38
  "label.visibility-private": "private",
39
- "label.visibility-public": "public"
39
+ "label.visibility-public": "public",
40
+ "text.figure-type-map-changed": "the type map changed. reopen to draw it."
40
41
  }
@@ -36,5 +36,6 @@
36
36
  "label.list": "一覧",
37
37
  "label.visibility-domain": "グループ内公開",
38
38
  "label.visibility-private": "プライベート",
39
- "label.visibility-public": "公開"
39
+ "label.visibility-public": "公開",
40
+ "text.figure-type-map-changed": "タイプマップが変わりました。開き直すと描画されます。"
40
41
  }
@@ -36,5 +36,6 @@
36
36
  "label.list": "목록",
37
37
  "label.visibility-domain": "그룹내 공개",
38
38
  "label.visibility-private": "비공개",
39
- "label.visibility-public": "공개"
39
+ "label.visibility-public": "공개",
40
+ "text.figure-type-map-changed": "타입 맵이 바뀌었습니다. 다시 열면 그려집니다."
40
41
  }