@things-factory/figure-ui 10.1.3

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 (112) hide show
  1. package/client/bootstrap.ts +16 -0
  2. package/client/graphql/index.ts +209 -0
  3. package/client/index.ts +2 -0
  4. package/client/modeller/figure-ask.ts +247 -0
  5. package/client/modeller/figure-canvas.ts +482 -0
  6. package/client/modeller/figure-inspector.ts +1054 -0
  7. package/client/modeller/figure-parts.ts +479 -0
  8. package/client/modeller/figure-preview.ts +381 -0
  9. package/client/modeller/figure-report.ts +895 -0
  10. package/client/modeller/figure-settings.ts +285 -0
  11. package/client/modeller/figure-side.ts +162 -0
  12. package/client/modeller/figure-source.test.ts +336 -0
  13. package/client/modeller/figure-source.ts +338 -0
  14. package/client/modeller/figure-view.ts +79 -0
  15. package/client/modeller/maturity.ts +288 -0
  16. package/client/modeller/part-edits.test.ts +252 -0
  17. package/client/modeller/part-edits.ts +246 -0
  18. package/client/modeller/proposal.ts +142 -0
  19. package/client/modeller/scene-budget.ts +102 -0
  20. package/client/pages/figure-list-page.ts +274 -0
  21. package/client/pages/figure-modeller-page.ts +1067 -0
  22. package/client/route.ts +11 -0
  23. package/client/themes/brand.css +51 -0
  24. package/client/themes/dark.css +64 -0
  25. package/client/themes/light.css +95 -0
  26. package/client/tsconfig.json +19 -0
  27. package/client/types.ts +96 -0
  28. package/client/viewparts/figure-card.ts +226 -0
  29. package/dist-client/bootstrap.d.ts +14 -0
  30. package/dist-client/bootstrap.js +17 -0
  31. package/dist-client/bootstrap.js.map +1 -0
  32. package/dist-client/graphql/index.d.ts +41 -0
  33. package/dist-client/graphql/index.js +173 -0
  34. package/dist-client/graphql/index.js.map +1 -0
  35. package/dist-client/index.d.ts +2 -0
  36. package/dist-client/index.js +3 -0
  37. package/dist-client/index.js.map +1 -0
  38. package/dist-client/modeller/figure-ask.d.ts +36 -0
  39. package/dist-client/modeller/figure-ask.js +256 -0
  40. package/dist-client/modeller/figure-ask.js.map +1 -0
  41. package/dist-client/modeller/figure-canvas.d.ts +128 -0
  42. package/dist-client/modeller/figure-canvas.js +431 -0
  43. package/dist-client/modeller/figure-canvas.js.map +1 -0
  44. package/dist-client/modeller/figure-inspector.d.ts +149 -0
  45. package/dist-client/modeller/figure-inspector.js +991 -0
  46. package/dist-client/modeller/figure-inspector.js.map +1 -0
  47. package/dist-client/modeller/figure-parts.d.ts +64 -0
  48. package/dist-client/modeller/figure-parts.js +478 -0
  49. package/dist-client/modeller/figure-parts.js.map +1 -0
  50. package/dist-client/modeller/figure-preview.d.ts +62 -0
  51. package/dist-client/modeller/figure-preview.js +360 -0
  52. package/dist-client/modeller/figure-preview.js.map +1 -0
  53. package/dist-client/modeller/figure-report.d.ts +134 -0
  54. package/dist-client/modeller/figure-report.js +881 -0
  55. package/dist-client/modeller/figure-report.js.map +1 -0
  56. package/dist-client/modeller/figure-settings.d.ts +39 -0
  57. package/dist-client/modeller/figure-settings.js +277 -0
  58. package/dist-client/modeller/figure-settings.js.map +1 -0
  59. package/dist-client/modeller/figure-side.d.ts +41 -0
  60. package/dist-client/modeller/figure-side.js +175 -0
  61. package/dist-client/modeller/figure-side.js.map +1 -0
  62. package/dist-client/modeller/figure-source.d.ts +196 -0
  63. package/dist-client/modeller/figure-source.js +153 -0
  64. package/dist-client/modeller/figure-source.js.map +1 -0
  65. package/dist-client/modeller/figure-view.d.ts +57 -0
  66. package/dist-client/modeller/figure-view.js +36 -0
  67. package/dist-client/modeller/figure-view.js.map +1 -0
  68. package/dist-client/modeller/maturity.d.ts +55 -0
  69. package/dist-client/modeller/maturity.js +212 -0
  70. package/dist-client/modeller/maturity.js.map +1 -0
  71. package/dist-client/modeller/part-edits.d.ts +64 -0
  72. package/dist-client/modeller/part-edits.js +218 -0
  73. package/dist-client/modeller/part-edits.js.map +1 -0
  74. package/dist-client/modeller/proposal.d.ts +45 -0
  75. package/dist-client/modeller/proposal.js +96 -0
  76. package/dist-client/modeller/proposal.js.map +1 -0
  77. package/dist-client/modeller/scene-budget.d.ts +78 -0
  78. package/dist-client/modeller/scene-budget.js +80 -0
  79. package/dist-client/modeller/scene-budget.js.map +1 -0
  80. package/dist-client/pages/figure-list-page.d.ts +40 -0
  81. package/dist-client/pages/figure-list-page.js +280 -0
  82. package/dist-client/pages/figure-list-page.js.map +1 -0
  83. package/dist-client/pages/figure-modeller-page.d.ts +219 -0
  84. package/dist-client/pages/figure-modeller-page.js +1045 -0
  85. package/dist-client/pages/figure-modeller-page.js.map +1 -0
  86. package/dist-client/route.d.ts +1 -0
  87. package/dist-client/route.js +11 -0
  88. package/dist-client/route.js.map +1 -0
  89. package/dist-client/themes/brand.css +51 -0
  90. package/dist-client/themes/dark.css +64 -0
  91. package/dist-client/themes/light.css +95 -0
  92. package/dist-client/types.d.ts +93 -0
  93. package/dist-client/types.js +2 -0
  94. package/dist-client/types.js.map +1 -0
  95. package/dist-client/viewparts/figure-card.d.ts +24 -0
  96. package/dist-client/viewparts/figure-card.js +221 -0
  97. package/dist-client/viewparts/figure-card.js.map +1 -0
  98. package/dist-server/index.d.ts +7 -0
  99. package/dist-server/index.js +3 -0
  100. package/dist-server/index.js.map +1 -0
  101. package/package.json +41 -0
  102. package/server/index.ts +7 -0
  103. package/server/tsconfig.json +18 -0
  104. package/test/brand-palette.test.ts +188 -0
  105. package/test/heading-scale-guard.test.ts +144 -0
  106. package/test/resolve-ts-siblings.mjs +67 -0
  107. package/things-factory.config.js +11 -0
  108. package/translations/en.json +247 -0
  109. package/translations/ja.json +247 -0
  110. package/translations/ko.json +247 -0
  111. package/translations/ms.json +247 -0
  112. package/translations/zh.json +247 -0
@@ -0,0 +1,274 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '../viewparts/figure-card.js'
3
+
4
+ import { css, html } from 'lit'
5
+ import { customElement, property, state } from 'lit/decorators.js'
6
+
7
+ import { i18next, localize } from '@operato/i18n'
8
+ import { navigate, PageView } from '@operato/shell'
9
+ import { ScrollbarStyles } from '@operato/styles'
10
+
11
+ import { deleteFigure, fetchFigureList } from '../graphql/index.js'
12
+ import type { Figure } from '../types.js'
13
+
14
+ const FigureListPageBase = localize(i18next)(PageView) as typeof PageView
15
+
16
+ /**
17
+ * Figure 목록.
18
+ *
19
+ * 이 화면은 저작면으로 가는 문이다. 여기서 오래 머물 화면이 아니므로 꾸미지 않는다.
20
+ * 대신 **저작자가 목록에서 판단할 수 있어야 하는 것**은 카드에 실는다 — 무게(삼각형
21
+ * 수)와 재활용 점수다. 그것을 저작면에 들어가서야 알면 늦다.
22
+ */
23
+ @customElement('figure-list-page')
24
+ export class FigureListPage extends FigureListPageBase {
25
+ static styles = [
26
+ ScrollbarStyles,
27
+ css`
28
+ :host {
29
+ display: flex;
30
+ flex-direction: column;
31
+ overflow: hidden;
32
+ background-color: var(--md-sys-color-surface);
33
+ color: var(--md-sys-color-on-surface);
34
+ }
35
+
36
+ /* 걸러 보기 줄 */
37
+ div[filters] {
38
+ display: flex;
39
+ align-items: center;
40
+ gap: var(--spacing-medium, 8px);
41
+ padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
42
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
43
+ }
44
+
45
+ input[search] {
46
+ flex: 1;
47
+ min-width: 0;
48
+ max-width: 320px;
49
+ padding: 6px 10px;
50
+ font: var(--input-field-font, inherit);
51
+ color: var(--md-sys-color-on-surface);
52
+ background-color: var(--md-sys-color-surface-container-lowest);
53
+ border: 1px solid var(--md-sys-color-outline-variant);
54
+ /* 명령 요소의 모서리는 테마가 정한다 — 알약으로 만들지 않는다 */
55
+ border-radius: 6px;
56
+ }
57
+
58
+ button[state] {
59
+ padding: 5px 12px;
60
+ font: var(--label-font, inherit);
61
+ color: var(--md-sys-color-on-surface);
62
+ background: none;
63
+ border: 1px solid var(--md-sys-color-outline-variant);
64
+ border-radius: 6px;
65
+ cursor: pointer;
66
+ }
67
+ button[state][selected] {
68
+ color: var(--md-sys-color-on-secondary-container);
69
+ background-color: var(--md-sys-color-secondary-container);
70
+ border-color: transparent;
71
+ }
72
+
73
+ span[count] {
74
+ margin-left: auto;
75
+ font: var(--label-font, inherit);
76
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
77
+ }
78
+
79
+ /* 카드 격자 */
80
+ div[cards] {
81
+ flex: 1;
82
+ overflow-y: auto;
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
85
+ gap: var(--spacing-medium, 8px);
86
+ padding: var(--spacing-large, 12px);
87
+ align-content: start;
88
+ }
89
+
90
+ /* ── 아무것도 없을 때 ──────────────────────────────────────────────
91
+ 이 자리가 이 화면에서 가장 중요하다. 처음 온 사람은 여기서 이 도구가
92
+ 무엇을 만드는 것인지 알게 된다. 「데이터 없음」으로 끝내지 않는다. */
93
+ div[empty] {
94
+ flex: 1;
95
+ display: flex;
96
+ flex-direction: column;
97
+ align-items: center;
98
+ justify-content: center;
99
+ gap: var(--spacing-medium, 8px);
100
+ padding: var(--spacing-large, 12px);
101
+ text-align: center;
102
+ color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
103
+ }
104
+ div[empty] md-icon {
105
+ --md-icon-size: 48px;
106
+ color: var(--md-sys-color-outline-variant);
107
+ }
108
+ div[empty] h2 {
109
+ margin: 0;
110
+ font: var(--theme-font, inherit);
111
+ font-size: var(--ui-t-title, 15px);
112
+ color: var(--md-sys-color-on-surface);
113
+ }
114
+ div[empty] p {
115
+ margin: 0;
116
+ max-width: 34em;
117
+ line-height: 1.6;
118
+ }
119
+ div[empty] button {
120
+ margin-top: var(--spacing-medium, 8px);
121
+ padding: 8px 18px;
122
+ font: var(--label-font, inherit);
123
+ color: var(--md-sys-color-on-primary, #fff);
124
+ background-color: var(--md-sys-color-primary);
125
+ border: none;
126
+ border-radius: 6px;
127
+ cursor: pointer;
128
+ }
129
+ `
130
+ ]
131
+
132
+ @state() private figures: Figure[] = []
133
+ @state() private total = 0
134
+ @state() private search = ''
135
+ @state() private stateFilter = ''
136
+ @state() private loaded = false
137
+
138
+ get context() {
139
+ return {
140
+ title: i18next.t('title.figures'),
141
+ actions: [
142
+ {
143
+ title: i18next.t('button.new-figure'),
144
+ action: () => this.openModeller()
145
+ }
146
+ ]
147
+ }
148
+ }
149
+
150
+ render() {
151
+ return html`
152
+ <div filters>
153
+ <input
154
+ search
155
+ type="search"
156
+ .value=${this.search}
157
+ placeholder=${i18next.t('text.search-figure-by-name-or-type')}
158
+ @change=${(e: Event) => {
159
+ this.search = (e.target as HTMLInputElement).value
160
+ this.reload()
161
+ }}
162
+ />
163
+ ${[
164
+ { value: '', label: i18next.t('label.all') },
165
+ { value: 'draft', label: i18next.t('label.draft') },
166
+ { value: 'released', label: i18next.t('label.released') }
167
+ ].map(
168
+ option => html`
169
+ <button
170
+ state
171
+ ?selected=${this.stateFilter === option.value}
172
+ @click=${() => {
173
+ this.stateFilter = option.value
174
+ this.reload()
175
+ }}
176
+ >
177
+ ${option.label}
178
+ </button>
179
+ `
180
+ )}
181
+ ${this.loaded ? html`<span count>${i18next.t('text.figure-count', { count: this.total })}</span>` : ''}
182
+ </div>
183
+
184
+ ${!this.loaded
185
+ ? html``
186
+ : this.figures.length === 0
187
+ ? this.renderEmpty()
188
+ : html`
189
+ <div cards>
190
+ ${this.figures.map(
191
+ figure => html`
192
+ <figure-card
193
+ .figure=${figure}
194
+ @open=${() => this.openModeller(figure.id)}
195
+ @remove=${() => this.removeFigure(figure)}
196
+ ></figure-card>
197
+ `
198
+ )}
199
+ </div>
200
+ `}
201
+ `
202
+ }
203
+
204
+ /**
205
+ * 빈 화면.
206
+ *
207
+ * 걸러 보기 때문에 비었는지, 아직 만든 것이 없어서 비었는지를 **가른다.** 같은
208
+ * 문구를 쓰면 사용자가 검색어를 지워 볼 생각을 못 한다.
209
+ */
210
+ private renderEmpty() {
211
+ const filtered = !!this.search || !!this.stateFilter
212
+
213
+ if (filtered) {
214
+ return html`
215
+ <div empty>
216
+ <md-icon>search_off</md-icon>
217
+ <h2>${i18next.t('text.no-figure-matches-the-filter')}</h2>
218
+ <p>${i18next.t('text.try-clearing-the-search-or-state-filter')}</p>
219
+ <button
220
+ @click=${() => {
221
+ this.search = ''
222
+ this.stateFilter = ''
223
+ this.reload()
224
+ }}
225
+ >
226
+ ${i18next.t('button.clear-filters')}
227
+ </button>
228
+ </div>
229
+ `
230
+ }
231
+
232
+ return html`
233
+ <div empty>
234
+ <md-icon>deployed_code</md-icon>
235
+ <h2>${i18next.t('text.no-figure-yet')}</h2>
236
+ <p>${i18next.t('text.what-a-figure-is')}</p>
237
+ <button @click=${() => this.openModeller()}>${i18next.t('button.new-figure')}</button>
238
+ </div>
239
+ `
240
+ }
241
+
242
+ async pageUpdated(changes: Record<string, unknown>, lifecycle: Record<string, unknown>) {
243
+ if (this.active) {
244
+ await this.reload()
245
+ }
246
+ }
247
+
248
+ private async reload() {
249
+ const { items, total } = await fetchFigureList({
250
+ search: this.search || undefined,
251
+ state: this.stateFilter || undefined
252
+ })
253
+
254
+ this.figures = items
255
+ this.total = total
256
+ this.loaded = true
257
+ }
258
+
259
+ private openModeller(id?: string) {
260
+ navigate(id ? `figure-modeller/${id}` : 'figure-modeller')
261
+ }
262
+
263
+ /** `remove` 는 HTMLElement 의 메서드다 — 가리면 조용한 결함이 된다. */
264
+ private async removeFigure(figure: Figure) {
265
+ // 되돌릴 수 없는 조작이므로 무엇이 사라지는지 이름으로 확인한다
266
+ const message = i18next.t('text.delete-figure-confirm', { name: figure.name })
267
+ if (!confirm(message)) {
268
+ return
269
+ }
270
+
271
+ await deleteFigure(figure.id)
272
+ await this.reload()
273
+ }
274
+ }