@things-factory/figure-ui 10.1.4 → 10.1.5
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/client/graphql/index.ts +87 -12
- package/client/modeller/figure-history-panel.ts +324 -0
- package/client/modeller/figure-side.ts +19 -1
- package/client/pages/figure-list-page.ts +363 -226
- package/client/pages/figure-modeller-page.ts +22 -0
- package/client/types.ts +17 -0
- package/dist-client/graphql/index.d.ts +24 -4
- package/dist-client/graphql/index.js +70 -9
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +42 -0
- package/dist-client/modeller/figure-history-panel.js +343 -0
- package/dist-client/modeller/figure-history-panel.js.map +1 -0
- package/dist-client/modeller/figure-side.d.ts +6 -0
- package/dist-client/modeller/figure-side.js +31 -0
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/pages/figure-list-page.d.ts +44 -21
- package/dist-client/pages/figure-list-page.js +351 -246
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +9 -0
- package/dist-client/pages/figure-modeller-page.js +20 -0
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +19 -0
- package/dist-client/types.js.map +1 -1
- package/package.json +4 -2
- package/translations/en.json +15 -0
- package/translations/ja.json +17 -2
- package/translations/ko.json +17 -2
- package/translations/ms.json +17 -2
- package/translations/zh.json +17 -2
- package/client/viewparts/figure-card.ts +0 -226
- package/dist-client/viewparts/figure-card.d.ts +0 -24
- package/dist-client/viewparts/figure-card.js +0 -221
- package/dist-client/viewparts/figure-card.js.map +0 -1
package/client/graphql/index.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import gql from 'graphql-tag'
|
|
2
2
|
import { client } from '@operato/graphql'
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
Figure,
|
|
6
|
+
FigureFinding,
|
|
7
|
+
FigureListResult,
|
|
8
|
+
FigurePatch,
|
|
9
|
+
FigureProposal,
|
|
10
|
+
FigureVersion,
|
|
11
|
+
NewFigure
|
|
12
|
+
} from '../types.js'
|
|
5
13
|
|
|
6
14
|
/**
|
|
7
15
|
* 응답에서 값을 꺼낸다. **서버가 말한 사유를 그대로 올린다.**
|
|
@@ -39,6 +47,7 @@ const FIGURE_LIST_FIELDS = `
|
|
|
39
47
|
description
|
|
40
48
|
category
|
|
41
49
|
state
|
|
50
|
+
version
|
|
42
51
|
score
|
|
43
52
|
triangles
|
|
44
53
|
groups
|
|
@@ -48,17 +57,19 @@ const FIGURE_LIST_FIELDS = `
|
|
|
48
57
|
updater { id name }
|
|
49
58
|
`
|
|
50
59
|
|
|
60
|
+
/**
|
|
61
|
+
* 목록을 가져온다.
|
|
62
|
+
*
|
|
63
|
+
* 걸러 보기·정렬·쪽 나누기를 **그대로 넘긴다.** 화면이 `search`·`state` 같은 이름을 따로 만들어
|
|
64
|
+
* filter 로 옮기던 것을 그만두었다 — 그 이름들은 격자(ox-grist)가 이미 컬럼 설정에서 만들어 주고,
|
|
65
|
+
* 중간에 한 벌 더 두면 격자가 아는 조건과 서버에 가는 조건이 어긋난다.
|
|
66
|
+
*/
|
|
51
67
|
export async function fetchFigureList(params: {
|
|
52
68
|
page?: number
|
|
53
69
|
limit?: number
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
state?: string
|
|
70
|
+
filters?: unknown[]
|
|
71
|
+
sortings?: { name: string; desc?: boolean }[]
|
|
57
72
|
}): Promise<FigureListResult> {
|
|
58
|
-
const filters: { name: string; operator: string; value: unknown }[] = []
|
|
59
|
-
if (params.category) filters.push({ name: 'category', operator: 'eq', value: params.category })
|
|
60
|
-
if (params.state) filters.push({ name: 'state', operator: 'eq', value: params.state })
|
|
61
|
-
|
|
62
73
|
const response = await client.query({
|
|
63
74
|
query: gql`
|
|
64
75
|
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
@@ -69,9 +80,9 @@ export async function fetchFigureList(params: {
|
|
|
69
80
|
}
|
|
70
81
|
`,
|
|
71
82
|
variables: {
|
|
72
|
-
filters,
|
|
83
|
+
filters: params.filters ?? [],
|
|
73
84
|
pagination: { page: params.page ?? 1, limit: params.limit ?? 30 },
|
|
74
|
-
sortings: [{ name: 'updatedAt', desc: true }]
|
|
85
|
+
sortings: params.sortings?.length ? params.sortings : [{ name: 'updatedAt', desc: true }]
|
|
75
86
|
},
|
|
76
87
|
fetchPolicy: 'network-only'
|
|
77
88
|
})
|
|
@@ -88,7 +99,6 @@ export async function fetchFigure(id: string): Promise<Figure> {
|
|
|
88
99
|
${FIGURE_LIST_FIELDS}
|
|
89
100
|
source
|
|
90
101
|
properties
|
|
91
|
-
version
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
`,
|
|
@@ -145,7 +155,7 @@ export async function updateFigure(id: string, patch: FigurePatch): Promise<Save
|
|
|
145
155
|
mutation: gql`
|
|
146
156
|
mutation ($id: String!, $patch: FigurePatch!) {
|
|
147
157
|
updateFigure(id: $id, patch: $patch) {
|
|
148
|
-
figure { ${FIGURE_LIST_FIELDS} source
|
|
158
|
+
figure { ${FIGURE_LIST_FIELDS} source }
|
|
149
159
|
violations { code message at }
|
|
150
160
|
}
|
|
151
161
|
}
|
|
@@ -207,3 +217,68 @@ export async function proposeFigure(request: {
|
|
|
207
217
|
|
|
208
218
|
return unwrap<FigureProposal>(response, 'proposeFigure')
|
|
209
219
|
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 발행한다 — 판 번호가 오르고, 그 순간이 판본으로 남는다.
|
|
223
|
+
*
|
|
224
|
+
* 이미 발행된 것을 다시 발행하면 서버가 거절한다. 고쳐서 다시 내려면 저장이 먼저이고, 저장은
|
|
225
|
+
* 발행을 푼다(초안으로 돌아온다).
|
|
226
|
+
*/
|
|
227
|
+
export async function releaseFigure(id: string, comment?: string): Promise<Figure> {
|
|
228
|
+
const response = await client.mutate({
|
|
229
|
+
mutation: gql`
|
|
230
|
+
mutation ReleaseFigure($id: String!, $comment: String) {
|
|
231
|
+
releaseFigure(id: $id, comment: $comment) {
|
|
232
|
+
id
|
|
233
|
+
version
|
|
234
|
+
state
|
|
235
|
+
updatedAt
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
`,
|
|
239
|
+
variables: { id, comment }
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
return unwrap<Figure>(response, 'releaseFigure')
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** 옛 판을 초안으로 되살린다. 정본·속성·그림이 돌아오고 이름·설명은 그대로다. */
|
|
246
|
+
export async function revertFigureVersion(id: string, version: number): Promise<SaveResult> {
|
|
247
|
+
const response = await client.mutate({
|
|
248
|
+
mutation: gql`
|
|
249
|
+
mutation RevertFigureVersion($id: String!, $version: Float!) {
|
|
250
|
+
revertFigureVersion(id: $id, version: $version) {
|
|
251
|
+
figure { ${FIGURE_LIST_FIELDS} source }
|
|
252
|
+
violations { code message at }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
`,
|
|
256
|
+
variables: { id, version }
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
return unwrap<SaveResult>(response, 'revertFigureVersion')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** 발행된 판들 — 최근 것부터 열 개. */
|
|
263
|
+
export async function fetchFigureVersions(id: string): Promise<FigureVersion[]> {
|
|
264
|
+
const response = await client.query({
|
|
265
|
+
query: gql`
|
|
266
|
+
query FigureVersions($id: String!) {
|
|
267
|
+
figureVersions(id: $id) {
|
|
268
|
+
version
|
|
269
|
+
comment
|
|
270
|
+
state
|
|
271
|
+
updatedAt
|
|
272
|
+
updater { id name }
|
|
273
|
+
score
|
|
274
|
+
triangles
|
|
275
|
+
groups
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
`,
|
|
279
|
+
variables: { id },
|
|
280
|
+
fetchPolicy: 'network-only'
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return unwrap<FigureVersion[]>(response, 'figureVersions') ?? []
|
|
284
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js'
|
|
2
|
+
|
|
3
|
+
import { css, html, LitElement, nothing } from 'lit'
|
|
4
|
+
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
|
+
|
|
6
|
+
import { i18next, localize } from '@operato/i18n'
|
|
7
|
+
|
|
8
|
+
import { fetchFigureVersions, releaseFigure, revertFigureVersion } from '../graphql/index.js'
|
|
9
|
+
import type { FigureVersion } from '../types.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 이력 — **발행하는 자리이자, 발행된 판들을 보는 자리.**
|
|
13
|
+
*
|
|
14
|
+
* ## 왜 저장 옆이 아니라 여기인가
|
|
15
|
+
*
|
|
16
|
+
* 저장은 하루에 수십 번이고 발행은 드물다. 두 단추를 나란히 두면 저장하려다 발행을 누른다.
|
|
17
|
+
* 그리고 발행은 **소비처에게 하는 약속**이라, 무엇을 약속하는지(지금 몇 판인가 · 저장 안 한 것이
|
|
18
|
+
* 있나 · 지난 판이 무엇이었나)를 함께 보여 주는 자리에 있어야 한다.
|
|
19
|
+
*
|
|
20
|
+
* ## 저장하지 않은 것이 있으면 발행하지 않는다
|
|
21
|
+
*
|
|
22
|
+
* 발행은 **저장된 것**을 내보낸다. 화면에 고친 것이 남아 있는 채로 발행하면, 저작자가 보고 있는
|
|
23
|
+
* 것과 약속한 것이 다르다. 그래서 막고, 왜 막혔는지 적는다.
|
|
24
|
+
*
|
|
25
|
+
* ## 되돌리기는 초안으로 돌아온다
|
|
26
|
+
*
|
|
27
|
+
* 옛 판의 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 되살렸다고 그 판이 다시 발행되지는
|
|
28
|
+
* 않는다 — 다시 내보내려면 발행을 한 번 더 하고, 그때 번호는 이미 쓴 다음 번호를 받는다.
|
|
29
|
+
*/
|
|
30
|
+
@customElement('figure-history-panel')
|
|
31
|
+
export class FigureHistoryPanel extends localize(i18next)(LitElement) {
|
|
32
|
+
static styles = css`
|
|
33
|
+
:host {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
min-height: 0;
|
|
37
|
+
overflow-y: auto;
|
|
38
|
+
background-color: var(--md-sys-color-surface-container-lowest);
|
|
39
|
+
color: var(--md-sys-color-on-surface);
|
|
40
|
+
font: var(--label-font, inherit);
|
|
41
|
+
font-size: 0.76rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
:host([off]) {
|
|
45
|
+
display: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
section {
|
|
49
|
+
padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
|
|
50
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
h3 {
|
|
54
|
+
margin: 0 0 6px 0;
|
|
55
|
+
font-size: var(--ui-t-micro, 11px);
|
|
56
|
+
font-weight: 700;
|
|
57
|
+
letter-spacing: 0.04em;
|
|
58
|
+
text-transform: uppercase;
|
|
59
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* 지금 무엇인가 — 발행을 누르기 전에 읽는 한 줄 */
|
|
63
|
+
div[now] {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 6px;
|
|
67
|
+
margin-bottom: 8px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
span[state] {
|
|
71
|
+
padding: 2px 8px;
|
|
72
|
+
border-radius: 10px;
|
|
73
|
+
font-size: 0.7rem;
|
|
74
|
+
font-weight: 700;
|
|
75
|
+
color: var(--md-sys-color-on-secondary-container);
|
|
76
|
+
background-color: var(--md-sys-color-secondary-container);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
span[state][released] {
|
|
80
|
+
color: var(--md-sys-color-on-primary);
|
|
81
|
+
background-color: var(--md-sys-color-primary);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
span[ver] {
|
|
85
|
+
font-family: var(--mono-font, monospace);
|
|
86
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
input,
|
|
90
|
+
button {
|
|
91
|
+
font: var(--input-field-font, inherit);
|
|
92
|
+
font-size: 0.76rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
input[note] {
|
|
96
|
+
width: 100%;
|
|
97
|
+
box-sizing: border-box;
|
|
98
|
+
padding: 5px 8px;
|
|
99
|
+
color: var(--md-sys-color-on-surface);
|
|
100
|
+
background-color: var(--md-sys-color-surface-container-lowest);
|
|
101
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
102
|
+
border-radius: 6px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
button[release] {
|
|
106
|
+
width: 100%;
|
|
107
|
+
margin-top: 6px;
|
|
108
|
+
padding: 6px 0;
|
|
109
|
+
font-weight: 700;
|
|
110
|
+
color: var(--ui-on-accent, #fff);
|
|
111
|
+
background-color: var(--md-sys-color-primary);
|
|
112
|
+
border: 0;
|
|
113
|
+
border-radius: 6px;
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
button[release][disabled] {
|
|
118
|
+
opacity: 0.45;
|
|
119
|
+
cursor: default;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* 왜 못 누르는지 — 눌러 보고 알게 하지 않는다 */
|
|
123
|
+
p[why] {
|
|
124
|
+
margin: 6px 0 0 0;
|
|
125
|
+
font-size: 0.7rem;
|
|
126
|
+
line-height: 1.5;
|
|
127
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
p[failed] {
|
|
131
|
+
margin: 6px 0 0 0;
|
|
132
|
+
font-size: 0.7rem;
|
|
133
|
+
line-height: 1.5;
|
|
134
|
+
color: var(--md-sys-color-error);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ul {
|
|
138
|
+
margin: 0;
|
|
139
|
+
padding: 0;
|
|
140
|
+
list-style: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
li {
|
|
144
|
+
display: grid;
|
|
145
|
+
grid-template-columns: auto 1fr auto;
|
|
146
|
+
gap: 4px 8px;
|
|
147
|
+
align-items: baseline;
|
|
148
|
+
padding: 7px 0;
|
|
149
|
+
border-bottom: 1px dashed var(--md-sys-color-outline-variant);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
li:last-child {
|
|
153
|
+
border-bottom: 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
li span[no] {
|
|
157
|
+
font-family: var(--mono-font, monospace);
|
|
158
|
+
font-weight: 700;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
li span[when] {
|
|
162
|
+
grid-column: 2 / 4;
|
|
163
|
+
font-size: 0.7rem;
|
|
164
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
li span[comment] {
|
|
168
|
+
grid-column: 1 / 4;
|
|
169
|
+
line-height: 1.5;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
li button[revert] {
|
|
173
|
+
padding: 2px 8px;
|
|
174
|
+
color: var(--md-sys-color-on-surface);
|
|
175
|
+
background: none;
|
|
176
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
177
|
+
border-radius: 6px;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
li button[revert]:hover {
|
|
182
|
+
color: var(--md-sys-color-on-primary-container);
|
|
183
|
+
background-color: var(--md-sys-color-primary-container);
|
|
184
|
+
border-color: transparent;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
p[quiet] {
|
|
188
|
+
margin: 0;
|
|
189
|
+
line-height: 1.6;
|
|
190
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
191
|
+
}
|
|
192
|
+
`
|
|
193
|
+
|
|
194
|
+
/** 지금 열려 있는 도형. 없으면(새로 만드는 중) 발행할 것이 없다. */
|
|
195
|
+
@property({ type: String }) figureId?: string
|
|
196
|
+
@property({ type: String }) figureState?: 'draft' | 'released'
|
|
197
|
+
@property({ type: Number }) figureVersion = 0
|
|
198
|
+
/** 저장하지 않은 것이 있나 — 있으면 발행을 막는다. */
|
|
199
|
+
@property({ type: Boolean }) dirty = false
|
|
200
|
+
|
|
201
|
+
@state() private versions: FigureVersion[] = []
|
|
202
|
+
@state() private busy = false
|
|
203
|
+
@state() private failure = ''
|
|
204
|
+
|
|
205
|
+
updated(changes: Map<string, unknown>) {
|
|
206
|
+
if (changes.has('figureId') || changes.has('figureVersion')) {
|
|
207
|
+
void this.reload()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private async reload() {
|
|
212
|
+
if (!this.figureId) {
|
|
213
|
+
this.versions = []
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
this.versions = await fetchFigureVersions(this.figureId)
|
|
219
|
+
this.failure = ''
|
|
220
|
+
} catch (e) {
|
|
221
|
+
this.failure = (e as Error).message
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
render() {
|
|
226
|
+
return html`
|
|
227
|
+
<section>
|
|
228
|
+
<h3>${i18next.t('label.release')}</h3>
|
|
229
|
+
${this.figureId ? this.renderRelease() : html`<p quiet>${i18next.t('text.save-the-figure-before-releasing')}</p>`}
|
|
230
|
+
</section>
|
|
231
|
+
|
|
232
|
+
<section>
|
|
233
|
+
<h3>${i18next.t('label.released-versions')}</h3>
|
|
234
|
+
${this.versions.length
|
|
235
|
+
? html`<ul>
|
|
236
|
+
${this.versions.map(version => this.renderVersion(version))}
|
|
237
|
+
</ul>`
|
|
238
|
+
: html`<p quiet>${i18next.t('text.nothing-released-yet')}</p>`}
|
|
239
|
+
</section>
|
|
240
|
+
`
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private renderRelease() {
|
|
244
|
+
const released = this.figureState === 'released'
|
|
245
|
+
/* 막는 이유는 하나씩 말한다. 「발행할 수 없습니다」로 뭉치면 무엇을 하면 되는지 모른다. */
|
|
246
|
+
const why = this.dirty
|
|
247
|
+
? i18next.t('text.release-needs-a-save-first')
|
|
248
|
+
: released
|
|
249
|
+
? i18next.t('text.already-released-edit-to-release-again')
|
|
250
|
+
: ''
|
|
251
|
+
|
|
252
|
+
return html`
|
|
253
|
+
<div now>
|
|
254
|
+
<span state ?released=${released}>${i18next.t(released ? 'label.released' : 'label.draft')}</span>
|
|
255
|
+
<span ver>${i18next.t('text.version-number', { version: this.figureVersion ?? 0 })}</span>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<input note placeholder=${i18next.t('text.what-changed-in-this-release')} ?disabled=${!!why || this.busy} />
|
|
259
|
+
|
|
260
|
+
<button release ?disabled=${!!why || this.busy} @click=${() => this.release()}>
|
|
261
|
+
${this.busy ? i18next.t('text.releasing') : i18next.t('button.release')}
|
|
262
|
+
</button>
|
|
263
|
+
|
|
264
|
+
${why ? html`<p why>${why}</p>` : nothing}
|
|
265
|
+
${this.failure ? html`<p failed>${this.failure}</p>` : nothing}
|
|
266
|
+
`
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private renderVersion(version: FigureVersion) {
|
|
270
|
+
return html`
|
|
271
|
+
<li>
|
|
272
|
+
<span no>${i18next.t('text.version-number', { version: version.version })}</span>
|
|
273
|
+
<span when>
|
|
274
|
+
${version.updatedAt ? new Date(version.updatedAt).toLocaleString() : ''}
|
|
275
|
+
${version.updater?.name ? ` · ${version.updater.name}` : ''}
|
|
276
|
+
</span>
|
|
277
|
+
<button revert ?disabled=${this.busy} @click=${() => this.revert(version.version)}>
|
|
278
|
+
${i18next.t('button.revert')}
|
|
279
|
+
</button>
|
|
280
|
+
${version.comment ? html`<span comment>${version.comment}</span>` : nothing}
|
|
281
|
+
</li>
|
|
282
|
+
`
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private async release() {
|
|
286
|
+
const input = this.renderRoot.querySelector('input[note]') as HTMLInputElement
|
|
287
|
+
const comment = input?.value?.trim()
|
|
288
|
+
|
|
289
|
+
this.busy = true
|
|
290
|
+
this.failure = ''
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const figure = await releaseFigure(this.figureId!, comment || undefined)
|
|
294
|
+
if (input) input.value = ''
|
|
295
|
+
/* 정본을 든 자리가 갱신한다 — 여기서 페이지 상태를 고쳐 쓰지 않는다. */
|
|
296
|
+
this.dispatchEvent(new CustomEvent('figure-released', { detail: { figure }, bubbles: true, composed: true }))
|
|
297
|
+
await this.reload()
|
|
298
|
+
} catch (e) {
|
|
299
|
+
this.failure = (e as Error).message
|
|
300
|
+
} finally {
|
|
301
|
+
this.busy = false
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private async revert(version: number) {
|
|
306
|
+
/* 되돌아갈 수 없는 조작은 아니지만(판본은 남는다), 지금 화면의 것이 덮인다 — 이름으로 확인한다. */
|
|
307
|
+
if (!confirm(i18next.t('text.revert-to-version-confirm', { version }))) {
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
this.busy = true
|
|
312
|
+
this.failure = ''
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const result = await revertFigureVersion(this.figureId!, version)
|
|
316
|
+
this.dispatchEvent(new CustomEvent('figure-reverted', { detail: result, bubbles: true, composed: true }))
|
|
317
|
+
await this.reload()
|
|
318
|
+
} catch (e) {
|
|
319
|
+
this.failure = (e as Error).message
|
|
320
|
+
} finally {
|
|
321
|
+
this.busy = false
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './figure-inspector.js'
|
|
2
2
|
import './figure-report.js'
|
|
3
3
|
import './figure-settings.js'
|
|
4
|
+
import './figure-history-panel.js'
|
|
4
5
|
|
|
5
6
|
import { css, html, LitElement } from 'lit'
|
|
6
7
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
@@ -12,7 +13,7 @@ import type { BoardModel, PartModel } from './figure-source.js'
|
|
|
12
13
|
import { DEFAULT_VIEW } from './figure-view.js'
|
|
13
14
|
import type { ViewSettings } from './figure-view.js'
|
|
14
15
|
|
|
15
|
-
type Tab = 'properties' | 'verdict' | 'view'
|
|
16
|
+
type Tab = 'properties' | 'verdict' | 'view' | 'history'
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* 저작면 오른쪽 칸 — 속성과 판정을 탭으로 겹쳐 둔다.
|
|
@@ -117,6 +118,11 @@ export class FigureSide extends localize(i18next)(LitElement) {
|
|
|
117
118
|
곳이라 변환된 FigureSource 를 받는다. 변환은 모델러 페이지가 한 번만 해서 내려보낸다.
|
|
118
119
|
*/
|
|
119
120
|
@property({ type: String }) figureName = ''
|
|
121
|
+
/** 이력 탭이 발행하고 되돌리는 데 필요한 것들. 정본은 저작면이 든다. */
|
|
122
|
+
@property({ type: String }) figureId?: string
|
|
123
|
+
@property({ type: String }) figureState?: 'draft' | 'released'
|
|
124
|
+
@property({ type: Number }) figureVersion = 0
|
|
125
|
+
@property({ type: Boolean }) dirty = false
|
|
120
126
|
|
|
121
127
|
@property({ type: Object }) board?: BoardModel
|
|
122
128
|
@property({ type: Array }) parts: PartModel[] = []
|
|
@@ -135,6 +141,7 @@ export class FigureSide extends localize(i18next)(LitElement) {
|
|
|
135
141
|
${this.renderTab('properties', i18next.t('label.properties'))}
|
|
136
142
|
${this.renderTab('verdict', i18next.t('label.maturity'), this.instances)}
|
|
137
143
|
${this.renderTab('view', i18next.t('label.view'))}
|
|
144
|
+
${this.renderTab('history', i18next.t('label.history'))}
|
|
138
145
|
</div>
|
|
139
146
|
|
|
140
147
|
<figure-inspector
|
|
@@ -152,6 +159,17 @@ export class FigureSide extends localize(i18next)(LitElement) {
|
|
|
152
159
|
></figure-report>
|
|
153
160
|
|
|
154
161
|
<figure-settings ?off=${this.tab !== 'view'} .view=${this.view}></figure-settings>
|
|
162
|
+
|
|
163
|
+
<!--
|
|
164
|
+
이력은 감춰도 DOM 에 남긴다 — 다른 탭들과 같은 규칙이다. 떼었다 붙이면 판본을 다시 부른다.
|
|
165
|
+
-->
|
|
166
|
+
<figure-history-panel
|
|
167
|
+
?off=${this.tab !== 'history'}
|
|
168
|
+
.figureId=${this.figureId}
|
|
169
|
+
.figureState=${this.figureState}
|
|
170
|
+
.figureVersion=${this.figureVersion}
|
|
171
|
+
.dirty=${this.dirty}
|
|
172
|
+
></figure-history-panel>
|
|
155
173
|
`
|
|
156
174
|
}
|
|
157
175
|
|