@things-factory/figure-ui 10.1.3 → 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-ask.ts +14 -4
- package/client/modeller/figure-canvas.ts +53 -0
- package/client/modeller/figure-history-panel.ts +324 -0
- package/client/modeller/figure-inspector.ts +78 -2
- package/client/modeller/figure-side.ts +22 -1
- package/client/modeller/figure-thumbnail.ts +78 -0
- package/client/pages/figure-list-page.ts +391 -195
- package/client/pages/figure-modeller-page.ts +114 -19
- 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-ask.js +14 -4
- package/dist-client/modeller/figure-ask.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +11 -0
- package/dist-client/modeller/figure-canvas.js +42 -0
- package/dist-client/modeller/figure-canvas.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-inspector.d.ts +17 -0
- package/dist-client/modeller/figure-inspector.js +77 -2
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +7 -0
- package/dist-client/modeller/figure-side.js +43 -0
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-thumbnail.d.ts +3 -0
- package/dist-client/modeller/figure-thumbnail.js +59 -0
- package/dist-client/modeller/figure-thumbnail.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +45 -17
- package/dist-client/pages/figure-list-page.js +374 -209
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +27 -0
- package/dist-client/pages/figure-modeller-page.js +109 -19
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-client/types.d.ts +19 -0
- package/dist-client/types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +5 -3
- package/translations/en.json +19 -0
- package/translations/ja.json +21 -2
- package/translations/ko.json +21 -2
- package/translations/ms.json +21 -2
- package/translations/zh.json +21 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-list-page.js","sourceRoot":"","sources":["../../client/pages/figure-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '../viewparts/figure-card.js'\n\nimport { css, html } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { deleteFigure, fetchFigureList } from '../graphql/index.js'\nimport type { Figure } from '../types.js'\n\nconst FigureListPageBase = localize(i18next)(PageView) as typeof PageView\n\n/**\n * Figure 목록.\n *\n * 이 화면은 저작면으로 가는 문이다. 여기서 오래 머물 화면이 아니므로 꾸미지 않는다.\n * 대신 **저작자가 목록에서 판단할 수 있어야 하는 것**은 카드에 실는다 — 무게(삼각형\n * 수)와 재활용 점수다. 그것을 저작면에 들어가서야 알면 늦다.\n */\n@customElement('figure-list-page')\nexport class FigureListPage extends FigureListPageBase {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n\n /* 걸러 보기 줄 */\n div[filters] {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n input[search] {\n flex: 1;\n min-width: 0;\n max-width: 320px;\n padding: 6px 10px;\n font: var(--input-field-font, inherit);\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n /* 명령 요소의 모서리는 테마가 정한다 — 알약으로 만들지 않는다 */\n border-radius: 6px;\n }\n\n button[state] {\n padding: 5px 12px;\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n button[state][selected] {\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n border-color: transparent;\n }\n\n span[count] {\n margin-left: auto;\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 카드 격자 */\n div[cards] {\n flex: 1;\n overflow-y: auto;\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n align-content: start;\n }\n\n /* ── 아무것도 없을 때 ──────────────────────────────────────────────\n 이 자리가 이 화면에서 가장 중요하다. 처음 온 사람은 여기서 이 도구가\n 무엇을 만드는 것인지 알게 된다. 「데이터 없음」으로 끝내지 않는다. */\n div[empty] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n text-align: center;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n div[empty] md-icon {\n --md-icon-size: 48px;\n color: var(--md-sys-color-outline-variant);\n }\n div[empty] h2 {\n margin: 0;\n font: var(--theme-font, inherit);\n font-size: var(--ui-t-title, 15px);\n color: var(--md-sys-color-on-surface);\n }\n div[empty] p {\n margin: 0;\n max-width: 34em;\n line-height: 1.6;\n }\n div[empty] button {\n margin-top: var(--spacing-medium, 8px);\n padding: 8px 18px;\n font: var(--label-font, inherit);\n color: var(--md-sys-color-on-primary, #fff);\n background-color: var(--md-sys-color-primary);\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n `\n ]\n\n @state() private figures: Figure[] = []\n @state() private total = 0\n @state() private search = ''\n @state() private stateFilter = ''\n @state() private loaded = false\n\n get context() {\n return {\n title: i18next.t('title.figures'),\n actions: [\n {\n title: i18next.t('button.new-figure'),\n action: () => this.openModeller()\n }\n ]\n }\n }\n\n render() {\n return html`\n <div filters>\n <input\n search\n type=\"search\"\n .value=${this.search}\n placeholder=${i18next.t('text.search-figure-by-name-or-type')}\n @change=${(e: Event) => {\n this.search = (e.target as HTMLInputElement).value\n this.reload()\n }}\n />\n ${[\n { value: '', label: i18next.t('label.all') },\n { value: 'draft', label: i18next.t('label.draft') },\n { value: 'released', label: i18next.t('label.released') }\n ].map(\n option => html`\n <button\n state\n ?selected=${this.stateFilter === option.value}\n @click=${() => {\n this.stateFilter = option.value\n this.reload()\n }}\n >\n ${option.label}\n </button>\n `\n )}\n ${this.loaded ? html`<span count>${i18next.t('text.figure-count', { count: this.total })}</span>` : ''}\n </div>\n\n ${!this.loaded\n ? html``\n : this.figures.length === 0\n ? this.renderEmpty()\n : html`\n <div cards>\n ${this.figures.map(\n figure => html`\n <figure-card\n .figure=${figure}\n @open=${() => this.openModeller(figure.id)}\n @remove=${() => this.removeFigure(figure)}\n ></figure-card>\n `\n )}\n </div>\n `}\n `\n }\n\n /**\n * 빈 화면.\n *\n * 걸러 보기 때문에 비었는지, 아직 만든 것이 없어서 비었는지를 **가른다.** 같은\n * 문구를 쓰면 사용자가 검색어를 지워 볼 생각을 못 한다.\n */\n private renderEmpty() {\n const filtered = !!this.search || !!this.stateFilter\n\n if (filtered) {\n return html`\n <div empty>\n <md-icon>search_off</md-icon>\n <h2>${i18next.t('text.no-figure-matches-the-filter')}</h2>\n <p>${i18next.t('text.try-clearing-the-search-or-state-filter')}</p>\n <button\n @click=${() => {\n this.search = ''\n this.stateFilter = ''\n this.reload()\n }}\n >\n ${i18next.t('button.clear-filters')}\n </button>\n </div>\n `\n }\n\n return html`\n <div empty>\n <md-icon>deployed_code</md-icon>\n <h2>${i18next.t('text.no-figure-yet')}</h2>\n <p>${i18next.t('text.what-a-figure-is')}</p>\n <button @click=${() => this.openModeller()}>${i18next.t('button.new-figure')}</button>\n </div>\n `\n }\n\n async pageUpdated(changes: Record<string, unknown>, lifecycle: Record<string, unknown>) {\n if (this.active) {\n await this.reload()\n }\n }\n\n private async reload() {\n const { items, total } = await fetchFigureList({\n search: this.search || undefined,\n state: this.stateFilter || undefined\n })\n\n this.figures = items\n this.total = total\n this.loaded = true\n }\n\n private openModeller(id?: string) {\n navigate(id ? `figure-modeller/${id}` : 'figure-modeller')\n }\n\n /** `remove` 는 HTMLElement 의 메서드다 — 가리면 조용한 결함이 된다. */\n private async removeFigure(figure: Figure) {\n // 되돌릴 수 없는 조작이므로 무엇이 사라지는지 이름으로 확인한다\n const message = i18next.t('text.delete-figure-confirm', { name: figure.name })\n if (!confirm(message)) {\n return\n }\n\n await deleteFigure(figure.id)\n await this.reload()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-list-page.js","sourceRoot":"","sources":["../../client/pages/figure-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist'\nimport '@material/web/icon/icon.js'\n\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { auth } from '@things-factory/auth-base/dist-client'\nimport { navigate, PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\nimport { capacityFrom } from '../modeller/scene-budget.js'\nimport { deleteFigure, fetchFigureList } from '../graphql/index.js'\nimport type { Figure } from '../types.js'\n\n/**\n * 이 값 아래면 점수를 색으로 눈에 띄게 한다.\n *\n * **등급 경계가 아니다.** 등급(A~D)은 `figure-model` 의 `scoreOf` 가 정하고 화면은 그것을\n * 베끼지 않는다. 이것은 「눈에 걸리게 할 선」이고, 화면의 판단이다.\n */\nconst ATTENTION_BELOW = 50\n\n/*\n * ── renderer 가 그리는 것은 이 파일의 CSS 가 닿지 않는다 ──────────────────────\n *\n * 컬럼 renderer 의 결과는 `ox-card-field` 의 shadow root 안에 그려진다. 이 페이지의 `static\n * styles` 는 그 경계를 넘지 못하므로 `code[figure-type]` 같은 선택자는 **아무 일도 하지 않는다.**\n * 넘어가는 것은 custom property 뿐이라, 색과 글꼴은 여기서 인라인으로 준다 — 값 자체는 테마\n * 토큰을 가리키므로 밝음·어둠은 따라온다. data-grist 의 image renderer 도 같은 방식이다.\n */\nconst CAPACITY = 'font-weight: 700; color: var(--md-sys-color-primary);'\nconst UNMEASURED = 'font-style: italic; color: var(--md-sys-color-outline);'\nconst RELEASED = 'font-weight: 700; color: var(--md-sys-color-primary);'\nconst DRAFT = 'color: var(--md-sys-color-tertiary);'\nconst badge = (low: boolean) =>\n `padding: 0 6px; border-radius: var(--md-sys-shape-corner-small, 4px); font-weight: 700; ${\n low\n ? 'color: var(--md-sys-color-on-error, #fff); background-color: var(--md-sys-color-error);'\n : 'color: var(--md-sys-color-on-primary-container); background-color: var(--md-sys-color-primary-container);'\n }`\n\n/*\n * `as typeof PageView` 로 조이지 않는다 — 그렇게 하면 p13n 이 얹어 주는\n * `getPagePreferenceProvider` 가 타입에서 사라져 개인 설정을 붙일 수 없다.\n */\nconst FigureListPageBase = p13n(localize(i18next)(PageView))\n\n/**\n * Figure 카탈로그.\n *\n * 이 화면은 저작면으로 가는 문이다. 여기서 오래 머물 화면이 아니므로 꾸미지 않는다. 카드에는\n * **고를 때 쓰는 것만** 싣는다 — 그림과 이름, 그리고 아직 초안이라는 것. 배치 한도·성숙도·삼각형\n * 수는 고른 뒤에 따지는 값이라 표 모드의 칸으로 남기고 저작면에서 본다.\n *\n * ## 왜 손으로 만든 카드가 아니라 격자인가\n *\n * 전에는 카드 격자와 걸러 보기 줄과 빈 화면을 이 파일이 직접 그렸다(200줄). 그 자리에서 빠져\n * 있던 것들은 하나같이 `ox-grist` 가 이미 가진 것이다 — 쪽 나누기, 정렬, 컬럼 걸러 보기,\n * 개인 설정(모드·정렬·쪽 크기를 사람마다 기억한다), 표 모드로 바꿔 보기, 내보내기.\n * 손으로 만든 카드는 그 중 아무것도 없었고, 목록이 늘어나면 전부 한 번에 그렸다.\n *\n * 카드에 실을 것은 `list` 가 정하고, 무엇을 어떻게 그릴지는 컬럼의 renderer 가 정한다. 같은\n * 설정이 표 모드에서도 그대로 쓰이므로 **두 모드가 다른 말을 하지 않는다.**\n */\n@customElement('figure-list-page')\nexport class FigureListPage extends FigureListPageBase {\n static styles = [\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n width: 100%;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n\n /* 격자가 바깥에 요구하는 것은 확정된 높이 하나뿐이다 — 스크롤은 격자가 안에서 한다 */\n ox-grist {\n flex: 1;\n min-height: 0;\n }\n\n /*\n ── 그림이 주인공인 카탈로그 ────────────────────────────────────────\n 카드 기본값은 글이 많은 목록(직원·결재함)에 맞춰져 있다. 여기서는 사람이 먼저 보는 것이\n 형상 그림이므로, 그림 칸을 비율로 잡아 카드 폭이 달라져도 그림이 나란히 읽히게 한다.\n */\n ox-grist {\n --data-card-min-width: 240px;\n --data-card-thumbnail-height: auto;\n --data-card-thumbnail-aspect: 4 / 3;\n --data-card-thumbnail-padding: var(--spacing-small);\n --data-card-thumbnail-background: var(--md-sys-color-surface-container);\n\n /*\n 카드 기본 글꼴은 굵다(제목 bold 1.1em). 글이 정보를 나르는 목록에서는 맞지만, 여기서\n 정보를 나르는 것은 그림이다. 굵은 제목이 카드마다 서면 그림보다 글이 먼저 읽힌다 —\n 이름은 알아볼 정도로만, 타입은 곁글로 둔다.\n */\n --data-card-item-name-font: 500 0.9rem/1.35 var(--theme-font, 'Roboto');\n --data-card-item-name-color: var(--md-sys-color-on-surface);\n --data-card-item-disc-font: normal 0.72rem/1.2 var(--theme-font, 'Roboto');\n --data-card-item-disc-color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n --data-card-item-etc-font: normal 0.72rem/1.2 var(--theme-font, 'Roboto');\n\n /* 상태와 성숙도는 짧은 표식이다 — 두 줄로 세우지 않고 한 줄에 뉜다 */\n --data-card-detail-direction: row;\n }\n `\n ]\n\n @property({ type: Object }) gristConfig: any\n\n @query('ox-grist') private grist!: DataGrist\n\n private listen?: () => void\n\n get context() {\n return {\n title: i18next.t('title.figures'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || '',\n placeholder: i18next.t('text.search-figure-by-name-or-type')\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [\n {\n title: i18next.t('button.new-figure'),\n action: () => this.openModeller()\n }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n mode=\"CARD\"\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-filters-form autofocus without-search></ox-filters-form>\n </div>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n `\n }\n\n async pageInitialized() {\n this.gristConfig = {\n /*\n **쪽 나누기는 선택이 아니다.** 격자에 가상 스크롤이 없어 받은 것을 전부 DOM 에 그린다.\n 카드 모드는 끝까지 내려가면 다음 쪽을 이어 붙이므로, 한 쪽이 작아도 목록은 계속 읽힌다.\n */\n pagination: { pages: [24, 48, 96] },\n\n /*\n 아직 아무것도 없을 때 하는 말. **이 자리가 이 화면에서 가장 중요하다** — 처음 온 사람은\n 여기서 이 도구가 무엇을 만드는 것인지 알게 된다. 조건이 걸려 비었을 때는 격자가 스스로\n 다른 말을 한다(「찾는 것이 없다」). 그 둘을 섞지 않는 것이 격자 쪽에 이미 들어 있다.\n */\n emptyNote: {\n icon: 'deployed_code',\n title: i18next.t('text.no-figure-yet'),\n description: i18next.t('text.what-a-figure-is')\n },\n\n list: {\n thumbnail: 'thumbnail',\n /* 그림이 없는 카드도 자리는 지킨다 — 빈 사각형으로 두지 않고 왜 없는지 말한다 */\n emptyThumbnail: {\n icon: 'deployed_code',\n label: i18next.t('text.thumbnail-not-made-yet')\n },\n fields: ['name', 'type'],\n /*\n **카드에는 고를 때 쓰는 것만 싣는다.**\n\n 배치 한도·성숙도·삼각형 수·수정일시까지 실어 봤더니 카드가 명세표가 되었다. 카탈로그에서\n 사람이 하는 일은 「이거다」를 고르는 것이고, 그때 보는 것은 그림과 이름이다.\n\n 숫자는 **하나만** 남긴다 — 성숙도다. 셋 중 이것 하나가 나머지를 요약한다(배치 한도와\n 삼각형 수는 무게 한 가지만 말하는데, 성숙도는 무게·식별성·상태 표현까지 여러 축을 합친\n 값이다). 그 축별 내역과 나머지 숫자는 표 모드의 칸과 저작면에 있다.\n\n 이름표는 달지 않는다. 성숙도는 색과 숫자로 읽히고, 상태는 발행된 것에 아무 말도 하지\n 않는다(그것이 예사다) — 초안일 때만 「초안」이 뜬다.\n */\n details: ['state', 'score'],\n /* 삭제는 손이 갈 때만 나온다 — 고르려고 보는 자리에 되돌릴 수 없는 조작을 띄우지 않는다 */\n gutters: 'hover'\n },\n\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'delete',\n title: i18next.t('button.delete'),\n danger: true,\n width: 40,\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: Figure) => this.removeFigure(record)\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('label.name'),\n record: { editable: false },\n filter: 'search',\n sortable: true,\n width: 200\n },\n {\n /*\n 타입 이름은 **저장되는 식별자**다. 표시 이름과 다르게 보이게 두어야 보드가 무엇으로\n 이 형상을 찾는지 헷갈리지 않는다.\n */\n type: 'string',\n name: 'type',\n header: i18next.t('label.type'),\n record: {\n editable: false,\n renderer: (value: string) => html`<code>${value}</code>`\n },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'state',\n header: i18next.t('label.state'),\n record: {\n editable: false,\n /*\n **발행된 것에는 아무 말도 하지 않는다.** 카탈로그에 있는 것은 대개 발행된 것이라,\n 카드마다 「발행됨」이 붙으면 그 말이 아무것도 가르지 않고 자리만 먹는다. 판 번호도\n 고를 때 쓰는 값이 아니다 — 이력 탭에 있다.\n\n 표 모드에서는 칸에 값이 있어야 하므로 그때는 발행됨도 적는다(`wide` 가 아니라 label\n 이 붙는 자리인지로 가른다 — 카드는 label 을 달지 않았다).\n */\n renderer: (value: string, _column: any, record: Figure, _rowIndex: number, field: Element) => {\n if (value !== 'released') {\n return html`<span style=${DRAFT}>${i18next.t('label.draft')}</span>`\n }\n\n /* 표에서는 칸이 비면 안 된다. 카드에서만 입을 다문다 — 그리는 자리가 알려 준다. */\n return field?.tagName === 'OX-CARD-FIELD'\n ? ''\n : html`<span style=${RELEASED}\n >${i18next.t('label.released')}\n ${record.version ? i18next.t('text.version-number', { version: record.version }) : ''}</span\n >`\n }\n },\n /* 전체·초안·발행됨을 단추로 고른다 — 값이 둘뿐인 조건을 펼침 목록에 넣지 않는다 */\n filter: {\n type: 'select-buttons',\n operator: 'in',\n options: [\n { display: i18next.t('label.draft'), value: 'draft' },\n { display: i18next.t('label.released'), value: 'released' }\n ]\n },\n width: 120\n },\n {\n /*\n **몇 개까지 놓을 수 있나.** 카드에는 싣지 않고 표 모드의 칸으로 둔다 — 고른 뒤에\n 따지는 값이다. 서버에 없는 값이라 `groups` 와 `triangles` 에서 계산하는데, 저작면\n 머리와 **같은 함수**를 쓴다. 두 화면이 같은 형상을 다른 잣대로 말하면 무엇을 고칠지\n 모른다.\n */\n type: 'string',\n name: 'capacity',\n header: i18next.t('label.capacity'),\n label: true,\n sortable: false,\n record: {\n editable: false,\n renderer: (_value: unknown, _column: any, record: Figure) => {\n const capacity = capacityFrom(record.groups ?? 0, record.triangles ?? 0)\n\n return capacity\n ? html`<span style=${CAPACITY} title=${i18next.t('text.reuse-score-explained')}>\n ${i18next.t('text.fits-n-instances', { n: capacity.instances.toLocaleString() })}\n </span>`\n : html`<span style=${UNMEASURED}>${i18next.t('text.not-measured-yet')}</span>`\n }\n },\n width: 120\n },\n {\n type: 'float',\n name: 'score',\n header: i18next.t('label.maturity'),\n record: {\n editable: false,\n /*\n 카드에 남긴 하나뿐인 숫자. **이름표를 달지 않으므로** 무엇인지는 색과 손이 갔을 때\n 뜨는 말이 알려 준다.\n\n 아직 재지 않은 것을 **0 으로 보여 주지 않는다.** 0 은 「가볍다」는 뜻이 되어 버린다.\n 등급 문자(A~D)도 여기서 만들지 않는다 — 경계는 `figure-model` 이 정하고 그것을\n 화면이 베끼면 두 벌이 되어 목록과 저작면이 다른 등급을 말한다.\n */\n renderer: (value: number | null) =>\n value == null\n ? html`<span style=${UNMEASURED}>${i18next.t('text.not-measured-yet')}</span>`\n : html`<span style=${badge(value < ATTENTION_BELOW)} title=${i18next.t('label.maturity')}\n >${Math.round(value)}</span\n >`\n },\n sortable: true,\n width: 90\n },\n {\n type: 'integer',\n name: 'triangles',\n header: i18next.t('label.triangles'),\n label: true,\n record: {\n editable: false,\n renderer: (value: number | null) => (value == null ? '' : value.toLocaleString())\n },\n sortable: true,\n width: 110\n },\n {\n type: 'integer',\n name: 'groups',\n header: i18next.t('label.budget-groups'),\n record: { editable: false },\n hidden: true,\n width: 0\n },\n {\n type: 'image',\n name: 'thumbnail',\n header: i18next.t('field.thumbnail'),\n record: { editable: false, align: 'center', wide: true },\n hidden: true,\n width: 160\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n label: true,\n record: { editable: false },\n sortable: true,\n width: 180\n },\n {\n /*\n `resource-object` 를 쓰지 않는다 — 그 renderer 는 다른 패키지(resource-ui)가 등록하고,\n 등록되지 않은 앱에서는 조용히 글자 renderer 로 떨어져 `[object Object]` 가 된다.\n 이름 하나 꺼내는 일에 그 위험을 질 이유가 없다.\n */\n type: 'string',\n name: 'updater',\n header: i18next.t('field.updater'),\n record: {\n editable: false,\n renderer: (value: { name?: string } | null) => value?.name || ''\n },\n hidden: true,\n width: 120\n }\n ],\n\n rows: {\n selectable: false,\n appendable: false,\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: Figure) => {\n if (record?.id) {\n this.openModeller(record.id)\n }\n }\n }\n },\n\n sorters: [{ name: 'updatedAt', desc: true }]\n }\n }\n\n connectedCallback() {\n super.connectedCallback()\n\n /*\n **로그인 정보가 도착하면 다시 불러온다.**\n\n 화면이 서는 것과 프로필이 오는 것은 순서가 정해져 있지 않고, 목록 질의는 로그인된 사람으로\n 나간다. 첫 질의가 프로필보다 앞서면 그 한 번이 실패하고, 그 뒤로 다시 묻는 사람이 없으면\n 화면은 비어 있는 채로 남는다. lite-menu 도 같은 신호로 같은 순서 문제를 푼다\n (`auth.on('profile')` — 그쪽 주석에 그 사연이 적혀 있다).\n */\n this.listen = () => {\n if (this.active) {\n this.grist?.fetch()\n }\n }\n auth.on('profile', this.listen)\n }\n\n disconnectedCallback() {\n if (this.listen) {\n auth.off?.('profile', this.listen)\n this.listen = undefined\n }\n super.disconnectedCallback()\n }\n\n async pageUpdated() {\n if (this.active) {\n this.grist.fetch()\n }\n }\n\n /**\n * 걸러 보기·정렬·쪽을 **격자가 만든 그대로** 서버에 넘긴다.\n *\n * 여기서 이름을 바꿔 담으면 격자가 아는 조건과 서버에 가는 조건이 어긋난다. 실패는 격자가\n * 받아 화면에 낸다 — 목록이 비어 있는 것과 못 불러온 것은 다른 사실이다.\n */\n async fetchHandler({ page = 1, limit = 24, sortings = [], filters = [] }: FetchOption) {\n const { items, total } = await fetchFigureList({ page, limit, filters, sortings })\n\n return { total: total || 0, records: items || [] }\n }\n\n private openModeller(id?: string) {\n navigate(id ? `figure-modeller/${id}` : 'figure-modeller')\n }\n\n /** `remove` 는 HTMLElement 의 메서드다 — 가리면 조용한 결함이 된다. */\n private async removeFigure(figure: Figure) {\n // 되돌릴 수 없는 조작이므로 무엇이 사라지는지 이름으로 확인한다\n const message = i18next.t('text.delete-figure-confirm', { name: figure.name })\n if (!confirm(message)) {\n return\n }\n\n await deleteFigure(figure.id)\n this.grist.fetch()\n }\n}\n"]}
|
|
@@ -70,6 +70,7 @@ export declare class FigureModellerPage extends FigureModellerPageBase {
|
|
|
70
70
|
willUpdate(changed: Map<string, unknown>): void;
|
|
71
71
|
get context(): {
|
|
72
72
|
title: string;
|
|
73
|
+
heading: boolean;
|
|
73
74
|
};
|
|
74
75
|
render(): import("lit-html").TemplateResult<1>;
|
|
75
76
|
/**
|
|
@@ -214,6 +215,32 @@ export declare class FigureModellerPage extends FigureModellerPageBase {
|
|
|
214
215
|
/** 지금 고른 부품 이름. 캔버스가 인덱스가 아니라 이름을 받는다. */
|
|
215
216
|
private get pickedName();
|
|
216
217
|
private pickPart;
|
|
218
|
+
/**
|
|
219
|
+
* 그림이 없는 도형을 **열었을 때 한 번** 찍어 채운다.
|
|
220
|
+
*
|
|
221
|
+
* ## 왜 여기인가
|
|
222
|
+
*
|
|
223
|
+
* 보드는 저장할 때 서버가 찍는다. 그런데 백엔드에 GPU 가 없는 배포가 많아 헤드리스로 3D 를 찍는
|
|
224
|
+
* 것이 자주 실패한다. 도형을 그리는 GPU 는 이미 보는 사람 손에 있고, 씬은 지금 여기 서 있다.
|
|
225
|
+
*
|
|
226
|
+
* 저작면을 거치지 않고 생긴 것들이 그림이 없다 — 씨앗으로 넣은 표본, AI 가 낸 후보, 반입한 자산.
|
|
227
|
+
* 그것들을 한 번 열어 보면 그때 채워진다.
|
|
228
|
+
*
|
|
229
|
+
* ## 저장이 아니다
|
|
230
|
+
*
|
|
231
|
+
* 그림만 보낸다. 정본을 건드리지 않으므로 미저장 표시도 켜지 않는다 — 열어 봤다는 것이 고쳤다는
|
|
232
|
+
* 뜻이 되면 저작자가 저장할지 말지를 매번 고민한다.
|
|
233
|
+
*/
|
|
234
|
+
/**
|
|
235
|
+
* 발행되었다 — **정본을 든 자리가 그 사실을 받는다.**
|
|
236
|
+
*
|
|
237
|
+
* 이력 패널은 발행을 시키고 결과를 알릴 뿐, 이 페이지의 상태를 고쳐 쓰지 않는다. 판 번호와 상태가
|
|
238
|
+
* 여기서 갱신되어야 머리줄·이력 탭이 같은 것을 본다.
|
|
239
|
+
*
|
|
240
|
+
* 정본(source)은 발행이 건드리지 않으므로 다시 불러오지 않는다 — 저작 중인 화면을 되감지 않는다.
|
|
241
|
+
*/
|
|
242
|
+
private released;
|
|
243
|
+
private fillThumbnail;
|
|
217
244
|
private save;
|
|
218
245
|
}
|
|
219
246
|
export {};
|
|
@@ -3,6 +3,7 @@ import '@material/web/icon/icon.js';
|
|
|
3
3
|
import '../modeller/figure-side.js';
|
|
4
4
|
import '../modeller/figure-ask.js';
|
|
5
5
|
import '../modeller/figure-canvas.js';
|
|
6
|
+
import { captureThumbnail } from '../modeller/figure-thumbnail.js';
|
|
6
7
|
import '../modeller/figure-preview.js';
|
|
7
8
|
import '../modeller/figure-parts.js';
|
|
8
9
|
import '../modeller/figure-inspector.js';
|
|
@@ -195,7 +196,13 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
195
196
|
display: none;
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
/*
|
|
199
|
+
/*
|
|
200
|
+
머리줄 — 이름 · 타입 · 미저장 표시 · 말로 시키는 줄 · 저장을 **한 줄에** 담는다.
|
|
201
|
+
|
|
202
|
+
이름은 페이지 제목에도 나온다(셸이 context.title 로 그린다). 그래도 여기 입력칸을 두는
|
|
203
|
+
이유는 **고치는 자리**가 필요해서다 — 제목은 읽는 것이고 이 칸은 바꾸는 것이다. 두 줄로
|
|
204
|
+
벌리는 대신 한 줄에 모아 캔버스에 높이를 넘긴다.
|
|
205
|
+
*/
|
|
199
206
|
div[header] {
|
|
200
207
|
display: flex;
|
|
201
208
|
align-items: center;
|
|
@@ -203,9 +210,13 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
203
210
|
padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
|
|
204
211
|
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
205
212
|
}
|
|
213
|
+
div[header] figure-ask {
|
|
214
|
+
flex: 1;
|
|
215
|
+
min-width: 0;
|
|
216
|
+
}
|
|
206
217
|
input[name] {
|
|
207
218
|
flex: none;
|
|
208
|
-
width:
|
|
219
|
+
width: 180px;
|
|
209
220
|
padding: 5px 9px;
|
|
210
221
|
font: var(--input-field-font, inherit);
|
|
211
222
|
font-size: 0.82rem;
|
|
@@ -483,37 +494,51 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
483
494
|
}
|
|
484
495
|
get context() {
|
|
485
496
|
return {
|
|
486
|
-
title: this.figure?.name ?? i18next.t('title.new-figure')
|
|
497
|
+
title: this.figure?.name ?? i18next.t('title.new-figure'),
|
|
498
|
+
/*
|
|
499
|
+
**제목 줄은 끈다.** 이름은 그대로 둔다.
|
|
500
|
+
|
|
501
|
+
이 화면의 주인공은 캔버스다. 이름은 속성 패널에서 보고 고치고, 브라우저 탭에도
|
|
502
|
+
「operato figure · 작업자」로 나온다. 그 위에 제목 줄을 한 겹 더 얹으면 같은 말이 세 번이고,
|
|
503
|
+
그만큼 캔버스가 낮아진다.
|
|
504
|
+
|
|
505
|
+
`title` 을 비우는 것으로 끄지 않는 이유는 그 값을 MDI 탭도 읽기 때문이다 — 비우면 탭 이름이
|
|
506
|
+
함께 사라진다. 이름은 하나이고 그리는 자리마다 켜고 끈다(`ox-page-title-bar`).
|
|
507
|
+
*/
|
|
508
|
+
heading: false
|
|
487
509
|
};
|
|
488
510
|
}
|
|
489
511
|
render() {
|
|
490
512
|
const source = this.folded;
|
|
491
513
|
return html `
|
|
492
514
|
<div header>
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
/>
|
|
499
|
-
<code type title=${i18next.t('text.type-name-cannot-change')}>${this.board?.figureType ?? ''}</code>
|
|
515
|
+
<!--
|
|
516
|
+
이름과 타입은 **속성 패널**에 있다(figure-inspector 의 도형 절). 여기 있던 이름 칸은
|
|
517
|
+
하네스 시절의 자리였다 — 앱에서는 셸이 페이지 제목으로 같은 이름을 그리므로 글자가 두 번
|
|
518
|
+
나오고, 캔버스가 주인공인 화면에서 그만큼 높이를 잃었다.
|
|
519
|
+
-->
|
|
500
520
|
${this.dirty ? html `<span dirty>${i18next.t('text.unsaved-changes')}</span>` : ''}
|
|
501
|
-
|
|
521
|
+
<!--
|
|
522
|
+
말로 시키는 줄이 머리줄의 남는 폭을 쓴다. 따로 두면 화면 위가 두 줄이 되고, 그 위 줄은
|
|
523
|
+
이름을 페이지 제목과 겹쳐 적는다.
|
|
524
|
+
-->
|
|
525
|
+
<figure-ask
|
|
526
|
+
.source=${source}
|
|
527
|
+
.type=${source?.type ?? ''}
|
|
528
|
+
@proposed=${(e) => this.receive(e.detail.proposal)}
|
|
529
|
+
></figure-ask>
|
|
502
530
|
<button save ?disabled=${!this.canSave} @click=${() => this.save()}>
|
|
503
531
|
${this.saving ? i18next.t('text.saving') : i18next.t('button.save')}
|
|
504
532
|
</button>
|
|
505
533
|
</div>
|
|
506
534
|
|
|
507
|
-
<figure-ask
|
|
508
|
-
.source=${source}
|
|
509
|
-
.type=${source?.type ?? ''}
|
|
510
|
-
@proposed=${(e) => this.receive(e.detail.proposal)}
|
|
511
|
-
></figure-ask>
|
|
512
|
-
|
|
513
535
|
<div
|
|
514
536
|
edits
|
|
515
537
|
?collapse-left=${this.collapsed.left}
|
|
516
538
|
?collapse-right=${this.collapsed.right}
|
|
539
|
+
@figure-rename=${(e) => this.rename(e.detail.name)}
|
|
540
|
+
@figure-released=${(e) => this.released(e.detail.figure)}
|
|
541
|
+
@figure-reverted=${() => this.figure?.id && this.load(this.figure.id)}
|
|
517
542
|
@add-part=${(e) => this.addPart(e.detail.primitive)}
|
|
518
543
|
@select-part=${(e) => (this.selected = e.detail.index)}
|
|
519
544
|
@remove-part=${(e) => this.removePart(e.detail.index)}
|
|
@@ -573,6 +598,11 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
573
598
|
</div>
|
|
574
599
|
|
|
575
600
|
<figure-side
|
|
601
|
+
.figureName=${this.figure?.name ?? ''}
|
|
602
|
+
.figureId=${this.figure?.id ?? ''}
|
|
603
|
+
.figureState=${this.figure?.state}
|
|
604
|
+
.figureVersion=${this.figure?.version ?? 0}
|
|
605
|
+
.dirty=${this.dirty}
|
|
576
606
|
.board=${this.board}
|
|
577
607
|
.parts=${this.parts}
|
|
578
608
|
.source=${source}
|
|
@@ -796,6 +826,10 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
796
826
|
async load(id) {
|
|
797
827
|
const figure = await fetchFigure(id);
|
|
798
828
|
this.figure = figure;
|
|
829
|
+
/* 그림이 없으면 이 자리에서 채운다(아래 `fillThumbnail`). 기다리지 않는다 — 화면이 먼저다. */
|
|
830
|
+
if (!figure.thumbnail && figure.id) {
|
|
831
|
+
void this.fillThumbnail(figure.id);
|
|
832
|
+
}
|
|
799
833
|
// 서버가 준 것을 그대로 쓴다. 읽지 못하면 오류보고없이 빈 상태로 바꾸지 않는다.
|
|
800
834
|
// 그러면 사용자가 저장을 선택해 원본을 덮어쓴다.
|
|
801
835
|
const model = figure.source ? fromFigureSource(JSON.parse(figure.source)) : undefined;
|
|
@@ -948,6 +982,51 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
948
982
|
if (at >= 0)
|
|
949
983
|
this.selected = at;
|
|
950
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* 그림이 없는 도형을 **열었을 때 한 번** 찍어 채운다.
|
|
987
|
+
*
|
|
988
|
+
* ## 왜 여기인가
|
|
989
|
+
*
|
|
990
|
+
* 보드는 저장할 때 서버가 찍는다. 그런데 백엔드에 GPU 가 없는 배포가 많아 헤드리스로 3D 를 찍는
|
|
991
|
+
* 것이 자주 실패한다. 도형을 그리는 GPU 는 이미 보는 사람 손에 있고, 씬은 지금 여기 서 있다.
|
|
992
|
+
*
|
|
993
|
+
* 저작면을 거치지 않고 생긴 것들이 그림이 없다 — 씨앗으로 넣은 표본, AI 가 낸 후보, 반입한 자산.
|
|
994
|
+
* 그것들을 한 번 열어 보면 그때 채워진다.
|
|
995
|
+
*
|
|
996
|
+
* ## 저장이 아니다
|
|
997
|
+
*
|
|
998
|
+
* 그림만 보낸다. 정본을 건드리지 않으므로 미저장 표시도 켜지 않는다 — 열어 봤다는 것이 고쳤다는
|
|
999
|
+
* 뜻이 되면 저작자가 저장할지 말지를 매번 고민한다.
|
|
1000
|
+
*/
|
|
1001
|
+
/**
|
|
1002
|
+
* 발행되었다 — **정본을 든 자리가 그 사실을 받는다.**
|
|
1003
|
+
*
|
|
1004
|
+
* 이력 패널은 발행을 시키고 결과를 알릴 뿐, 이 페이지의 상태를 고쳐 쓰지 않는다. 판 번호와 상태가
|
|
1005
|
+
* 여기서 갱신되어야 머리줄·이력 탭이 같은 것을 본다.
|
|
1006
|
+
*
|
|
1007
|
+
* 정본(source)은 발행이 건드리지 않으므로 다시 불러오지 않는다 — 저작 중인 화면을 되감지 않는다.
|
|
1008
|
+
*/
|
|
1009
|
+
released(figure) {
|
|
1010
|
+
if (!this.figure || figure?.id !== this.figure.id) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
this.figure = { ...this.figure, version: figure.version, state: figure.state, updatedAt: figure.updatedAt };
|
|
1014
|
+
}
|
|
1015
|
+
async fillThumbnail(id) {
|
|
1016
|
+
await this.updateComplete;
|
|
1017
|
+
const thumbnail = await captureThumbnail(this.board, this.parts);
|
|
1018
|
+
if (!thumbnail || this.figure?.id !== id) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
try {
|
|
1022
|
+
await updateFigure(id, { thumbnail });
|
|
1023
|
+
this.figure = { ...this.figure, thumbnail };
|
|
1024
|
+
}
|
|
1025
|
+
catch (e) {
|
|
1026
|
+
/* 볼 권한만 있는 사람일 수 있다. 화면은 그대로 두고 조용히 넘어간다. */
|
|
1027
|
+
console.warn(`[thumbnail] ${id} 를 채우지 못했습니다 — ${e.message}`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
951
1030
|
async save() {
|
|
952
1031
|
if (!this.canSave || !this.folded || !this.figure) {
|
|
953
1032
|
return;
|
|
@@ -955,10 +1034,20 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
955
1034
|
this.saving = true;
|
|
956
1035
|
try {
|
|
957
1036
|
const sourceJson = JSON.stringify(this.folded);
|
|
1037
|
+
/*
|
|
1038
|
+
보이는 것을 그대로 찍어 함께 보낸다.
|
|
1039
|
+
|
|
1040
|
+
카드가 그림 없이 서 있으면 카탈로그에서 무엇인지 못 고른다. 찍는 자리를 저장에 두는 이유는
|
|
1041
|
+
**그 순간이 정본이 바뀌는 순간**이라서다 — 따로 찍는 절차를 두면 그림과 정본이 갈린다.
|
|
1042
|
+
|
|
1043
|
+
못 찍으면 보내지 않는다(`undefined`). 빈 그림을 넣어 「그림이 있다」고 말하지 않는다.
|
|
1044
|
+
*/
|
|
1045
|
+
const thumbnail = await captureThumbnail(this.board, this.parts);
|
|
958
1046
|
if (this.figure.id) {
|
|
959
1047
|
const result = await updateFigure(this.figure.id, {
|
|
960
1048
|
name: this.figure.name,
|
|
961
|
-
source: sourceJson
|
|
1049
|
+
source: sourceJson,
|
|
1050
|
+
thumbnail
|
|
962
1051
|
});
|
|
963
1052
|
this.figure = result.figure;
|
|
964
1053
|
this.violations = result.violations;
|
|
@@ -973,7 +1062,8 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
973
1062
|
const result = await createFigure({
|
|
974
1063
|
type: this.folded.type,
|
|
975
1064
|
name: this.figure.name,
|
|
976
|
-
source: sourceJson
|
|
1065
|
+
source: sourceJson,
|
|
1066
|
+
thumbnail
|
|
977
1067
|
});
|
|
978
1068
|
this.figure = result.figure;
|
|
979
1069
|
this.violations = result.violations;
|